0

I've a four levels of nested array like this:

$array = [
    [
        'website' => [
            'id' => 'one'
        ],
        'children' => [
            [
                'website' => [
                    'id' => 'one.one'
                ],
                'children' => [
                    [
                        'website' => [
                            'id' => 'one.one.one'
                        ],
                        'children' => [
                            [
                                'website' => [
                                    'id' => 'one.one.one.one'
                                ],
                                'children' => []
                            ],
                            [
                                'website' => [
                                    'id' => 'one.one.one.two'
                                ],
                                'children' => []
                            ]
                        ]
                    ],
                    [
                        'website' => [
                            'id' => 'one.one.two'
                        ],
                        'children' => [
                            [
                                'website' => [
                                    'id' => 'one.one.two.one'
                                ],
                                'children' => []
                            ],
                            [
                                'website' => [
                                    'id' => 'one.one.two.two'
                                ],
                                'children' => []
                            ]
                        ]
                    ]
                ]
            ],
            [
                'website' => [
                    'id' => 'one.two'
                ],
                'children' => [
                    [
                        'website' => [
                            'id' => 'one.two.one'
                        ],
                        'children' => [
                            [
                                'website' => [
                                    'id' => 'one.two.one.one'
                                ],
                                'children' => []
                            ],
                            [
                                'website' => [
                                    'id' => 'one.two.one.two'
                                ],
                                'children' => []
                            ]
                        ]
                    ],
                    [
                        'website' => [
                            'id' => 'one.two.two'
                        ],
                        'children' => [
                            [
                                'website' => [
                                    'id' => 'one.two.two.one'
                                ],
                                'children' => []
                            ],
                            [
                                'website' => [
                                    'id' => 'one.two.two.two'
                                ],
                                'children' => []
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]
];

now, I'd like to remove some of the array element based on some rules. For simplicity, let's say, we'd like to remove array elements that has 'id' equals to one.one.two or one.two.one.

I was looking into some answers provided on stackoverflow and was trying to apply them to solve my issue, but didn't go much. The most chalanging thing is to unset part of an array where it's not the farthest point in the array.

I was trying this to note at which level I want to delete the arrays and then trying to unset them using those array index.

$pointer = [];
foreach($array as $key => $level1) {
    $level1pointer = $key;
    $pointer[] = markToDelete($level1, $level1pointer);
    foreach($level1['children'] as $key => $level2) {
        $level2pointer = $key;
        $pointer[] = markToDelete($level2, $level1pointer, $level2pointer);
        foreach($level2['children'] as $key => $level3) {
            $level3pointer = $key;
            $pointer[] = markToDelete($level3, $level1pointer, $level2pointer, $level3pointer);
            foreach($level3['children'] as $key => $level4) {
                $level4pointer = $key;
                $pointer[] = markToDelete($level4, $level1pointer, $level2pointer, $level3pointer, $level4pointer);
            }
        }
    }
}

function markToDelete($array, $level1 = null, $level2 = null, $level3 = null, $level4 = null) {
    $exclusionList = [
        'one.one.two',
        'one.two.one'
    ];

    if (!empty($array['website']) && in_array($array['website']['id'], $exclusionList)) {
        print_r('marking for deletion: '. $array['website']['id'] . PHP_EOL);
        return [
            'id' => $array['website']['id'],
            'level1' => $level1,
            'level2' => $level2,
            'level3' => $level3,
            'level4' => $level4
        ];
    }
    return [];
}

I was also trying to use Iterator like this:

$it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array), \RecursiveIteratorIterator::LEAVES_ONLY);
$newArray = [];
foreach($it as $key => $value) {
    $exclusionList = [
        'one.one.two',
        'one.two.one'
    ];
    if(!in_array($value, $exclusionList)) {
        print_r(sprintf('value: %s is ready to be deleted', $value).PHP_EOL);
        $newArray[] = $value;
    }
}

but I need a way to unset the array when looping through the iterator.

I'd like to get an output like this:

$array = [
    [
        'website' => [
            'id' => 'one'
        ],
        'children' => [
            [
                'website' => [
                    'id' => 'one.one'
                ],
                'children' => [
                    [
                        'website' => [
                            'id' => 'one.one.one'
                        ],
                        'children' => [
                            [
                                'website' => [
                                    'id' => 'one.one.one.one'
                                ],
                                'children' => []
                            ],
                            [
                                'website' => [
                                    'id' => 'one.one.one.two'
                                ],
                                'children' => []
                            ]
                        ]
                    ],
                ]
            ],
            [
                'website' => [
                    'id' => 'one.two'
                ],
                'children' => [
                    [
                        'website' => [
                            'id' => 'one.two.two'
                        ],
                        'children' => [
                            [
                                'website' => [
                                    'id' => 'one.two.two.one'
                                ],
                                'children' => []
                            ],
                            [
                                'website' => [
                                    'id' => 'one.two.two.two'
                                ],
                                'children' => []
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]
];

I'd really appreciate help on how to resolve this in more efficient way. Thanks.

Tuhin
  • 195
  • 1
  • 9
  • I've tried a few answers on stackoverflow like these: [Removing elements from multidimensional array](https://stackoverflow.com/questions/55697668/removing-elements-from-multidimensional-array), [how to remove an array from a multidimensional array?](https://stackoverflow.com/questions/40401192/how-to-remove-an-array-from-a-multidimensional-array), [Remove elements from recursive array from bottom to top in PHP](https://stackoverflow.com/questions/43679679/remove-elements-from-recursive-array-from-bottom-to-top-in-php) – Tuhin May 28 '19 at 22:16
  • In the end, Ive used the following code: ```php function removeInvalidEntries(&$urls) { foreach ($urls as $index => &$url) { if (empty($url['website'])) { continue; } if (!$this->urlValidityCheck($url)) { unset($urls[$index]); } $this->removeInvalidEntries($url['children']); } } function urlValidityCheck($url) { // some logic to check whether to delete this entry // and returning true or false return $result; } ``` – Tuhin Jun 07 '19 at 16:48

1 Answers1

1

Here's a recursive function that will do what you want. It traverses the array, looking for children whose website id is in the list of ids to be removed and unsetting them. Note that because of your additional top-level array, you need to iterate the function over those values.

function delete_entries(&$array, $ids_to_delete) {
    foreach ($array['children'] as $index => &$child) {
        if (in_array($child['website']['id'], $ids_to_delete)) {
            unset($array['children'][$index]);
        }
        delete_entries($child, $ids_to_delete);
    }
}

foreach ($array as &$arr) {
    delete_entries($arr, array('one.one.two', 'one.two.one'));
}

var_export($array);

The output is as you desire but too long to reproduce here. See the demo on 3v4l.org

Update

The above code won't delete entries on the top level because the array structure is different from lower levels in the array. That can be dealt with in the outer foreach loop:

$excluded = array('two', 'one.one.two', 'one.two.one');
foreach ($array as $key => &$arr) {
    if (in_array($arr['website']['id'], $excluded)) {
        unset($array[$key]);
    }
    else {
        delete_entries($arr, $excluded);
    }
}

Updated demo

Nick
  • 138,499
  • 22
  • 57
  • 95
  • Thanks a lot Nick. It certainly works for all the children, apart from the top level one. i.e. if I add `two` in `$ids_to_delete`, then it doesn't remove that part of the array. – Tuhin May 29 '19 at 07:58
  • sorry, I realised that in this example, we don't have `two` as I shortened the array. so I was saying, if I add `one` in `$ids_to_delete`, it should then return an empty array. which is unfortunately not working here. – Tuhin May 29 '19 at 08:16
  • @Tuhin you shouldn't accept an answer that doesn't actually solve your problem; it will discourage other people from answering. – Nick May 29 '19 at 12:48
  • @Tuhin see my edit. I've modified the `foreach` loop to check for entries which should be deleted at the top level of the array. I think that will resolve your issues. – Nick May 29 '19 at 12:52
  • thanks a lot Nick. Reason I expected the answer was because the solution you presented on the first time was enough for my problem. However, with the updates, you made a better solution. Thanks again. – Tuhin Jun 05 '19 at 21:41