Search code examples
phplaravelmultidimensional-arraynested-loops

Laravel merge into nested array with data from another array based on ID key


Been trying to solve this with help of Laravel collections or array helpers in most efficient way. Can anyone help with this?

$menu = [
    ["id"=>1],
    ["id"=>2],
    ["id"=>3,"children"=>[
        ["id"=>4]
    ]],
    ["id"=>5,"children"=>[
        ["id"=>6],
        ["id"=>7,"children"=>[
            ["id"=>8],
            ["id"=>9]
        ]]
    ]],
    ["id"=>10]
];

$pages = [
    ['id'=>1,'label'=>'About Us','url_path'=>'/about-us'],
    ['id'=>2,'label'=>'Meet the Team','url_path'=>'/meet-the-team'],
    ['id'=>3,'label'=>'Services','url_path'=>'/services'],
    ['id'=>4,'label'=>'Contact Us','url_path'=>'/contact-us'],
    ['id'=>5,'label'=>'Company Mission','url_path'=>'/company-mission'],
    ['id'=>6,'label'=>'History','url_path'=>'/history'],
    ...
    ...
];

The ID key from nested array $menu needs to search through $pages array based on same ID and then merge this whole row back to $menu.

My Function:

$menu = collect($menu)->map(function($item) use($pages){
    $page_item = collect($pages)->firstWhere('id',$item['id']);
    if(array_has($item,'children')){
        $page_item['children'] = ...
    }
    return $page_item;
})->toArray();

How do I add the array data from $pages to "children" with recursive iteration? And is there better way to make this function more efficient? I want to make use of Laravel collections or array helpers to solve this instead of using foreach loop, etc.

Below is the expected result:

$menu = [
    ["id"=>1,"label"=>"About Us","url_path"=>"/about-us"],
    ["id"=>2,"label"=>"Meet the Team","url_path"=>"/meet-the-team"],
    ["id"=>3,"label"=>"Services","url_path"=>"/services","children"=>[
        ["id"=>4,"label"=>"Contact Us","url_path"=>"/contact-us"]
    ]],
    ["id"=>5,"label"=>"Company Mission","url_path"=>"/company-mission","children"=>[
        ["id"=>6,"label"=>"History","url_path"=>"/history"],
        ["id"=>7, ...
    ...
];

Can anyone show me? Thanks!


Solution

  • You can achieve this by using a recursive function as,

    function map_pages_to_menu(array $pages, array $menu) {
        return collect($menu)->map(function ($menu_item) use ($pages){
    
            // find the page item that matches menu id
            $page_item = collect($pages)->firstWhere('id',$menu_item['id']);
    
            // if there is a match create the menu item from the page item
            $menu_item = $page_item ? array_merge($menu_item, $page_item) : [];
    
            // if there are children inside menu item, recursively find matching page items.
            if(array_has($menu_item,'children')){
                $menu_item['children'] = map_pages_to_menu($pages, $menu_item['children']);
            }
    
            return $menu_item;
        })
        // to remove empty items & reset index
        ->filter()->values()
        ->toArray();
    }
    
    $menu = '[
        {"id":1},
        {"id":2},
        {"id":3,"children":[
            {"id":4}
        ]},
        {"id":5,"children":[
            {"id":6},
            {"id":7,"children":[
                {"id":8},
                {"id":9}
            ]}
        ]},
        {"id":10}
    ]';
    
    $menu = json_decode($menu, true);
    
    $pages = [
        ['id'=>1,'label'=>'About Us','url_path'=>'/about-us'],
        ['id'=>2,'label'=>'Meet the Team','url_path'=>'/meet-the-team'],
        ['id'=>3,'label'=>'Services','url_path'=>'/services'],
        ['id'=>4,'label'=>'Contact Us','url_path'=>'/contact-us'],
        ['id'=>5,'label'=>'Company Mission','url_path'=>'/company-mission'],
        ['id'=>6,'label'=>'History','url_path'=>'/history']
    ];
    
    $menu = map_pages_to_menu($pages, $menu);
    
    echo json_encode($menu);
    

    Snippet : https://implode.io/yTmrDO