[PHP]Unsetting nested descendants of a particular ancestor based on an key and if empty unset the ancestor as well

Let’s imagine we have this multidimensional array which contains nested arrays

$arr = [
    1 => [
        'id' => 1,
        'families' => [
            0 => [
                'id' => 2
            ],
            1 => [
                'id' => 3
            ],
        ]
    ],
    2 => [
        'id' => 1,
        'families' => [
            0 => [
                'id' => 2,
                'products' => [
                    1 => 'John Doe'
                ],
            ],
            1 => [
                'id' => 3,
                'products' => [],
            ],
        ]
    ],
    3 => [
        'id' => 1,
        'products' => [
            1 => 'Hi',
            2 => 'Hello',
        ]
    ],
    4 => [
        'id' => 1,
        'families' => [
            0 => [
                'id' => 2
            ],
            1 => [
                'id' => 3
            ],
        ]
    ],
];

I need to keep all ancestors and descendants where there is at least one item in the key “products”, all other arrays should be unset.

So, in this particular example, the result should be as follows:

$arr = [
    2 => [
        'id' => 1,
        'families' => [
            0 => [
                'id' => 2,
                'products' => [
                    1 => 'John Doe'
                ],
            ],
        ]
    ],
    3 => [
        'id' => 1,
        'products' => [
            1 => 'Hi',
            2 => 'Hello',
        ]
    ],
];

Basically, what needs to be done is to go from the most inner array up and asking:

  1. Is our key “products” empty? Yes

  2. Is our key “families” either empty or not set? Yes

  3. unset this array

Any help is appreciated!

I renamed your arr to districts. It will make it easier to visualize.

// makes new array, note: not really necessary
$hasProducts = array();

// cycling through each district (your termed it arr)
foreach ($districts as $key => $district){
    
    // don't even bother with rest of code if either key is missing
    if (!isset($district['products']) && !isset($district['families'])) continue;
   
    // if products key exists on (arr) || (districts) copy it into new array 
    if (isset($district['products'])){
        $hasProducts[$key] = $districts[$key];
    }
    
    // if the key families exists, filter out any families with no products
    if (isset($district['families'])){
        $familiesWithProducts = array_filter($district['families'], function($family){
            return !empty($family['products']);
        });
        
        // if a family with products exists,. add it to array
        if (!empty($familiesWithProducts)){
            $hasProducts[$key] = $familiesWithProducts;
        }
        
    }
}

// enjoy
var_dump($hasProducts);
1 Like

Hi,

thank you for having looked at my problem and coming up with a solution. It’s still not working as I need to, but I’ll try and tweak your piece of code.

For the array above, the code should output:

$arr = [
    2 => [
        'id' => 1,
        'families' => [
            0 => [
                'id' => 2,
                'products' => [
                    1 => 'John Doe'
                ],
            ],
        ]
    ],
    3 => [
        'id' => 1,
        'products' => [
            1 => 'Hi',
            2 => 'Hello',
        ]
    ],
];

But instead we get :
(i.e. we’ve lost all info about $arr[2], in this particular case just its id, but what if there is more info? Also, the key “families” in $arr[2] got lost and instead of $arr[2][‘families’][0] we now have $arr[2][0])

$arr = [
	2 => [
		0 => [
			'id' => 2,
			'products' => [
				1 => 'John Doe'
			],
		],
	],
	3 => [
		'id' => 1,
		'products' => [
			1 => 'Hi',
			2 => 'Hello',
		]
	],
];

Edit:
(the aforementioned issues were solved by editing these two lines of code, however, new issues have popped up)

// if a family with products exists,. add it to array
		if (!empty($familiesWithProducts)){
			$hasProducts[$key] = $districts[$key];
			$hasProducts[$key]['families'] = $familiesWithProducts;
		}

If we alter the old array a bit to this:

$arr = [
	1 => [
		'id' => 1,
		'families' => [
			0 => [
				'id' => 2
			],
			1 => [
				'id' => 3,
				'families' => [
					0 => [
						'id' => 2,
						'products' => [
							1 => 'Arnold'
						],
					],
				]
			],
		]
	],
	2 => [
		'id' => 1,
		'families' => [
			0 => [
				'id' => 2,
				'products' => [
					1 => 'John Doe'
				],
			],
			1 => [
				'id' => 3,
				'products' => [],
			],
		]
	],
	3 => [
		'id' => 1,
		'products' => [
			1 => 'Hi',
			2 => 'Hello',
		]
	],
	4 => [
		'id' => 1,
		'families' => [
			0 => [
				'id' => 2
			],
			1 => [
				'id' => 3
			],
		]
	],
];

the result is:

$arr = [
	2 => [
		0 => [
			'id' => 2,
			'products' => [
				1 => 'John Doe'
			],
		],
	],
	3 => [
		'id' => 1,
		'products' => [
			1 => 'Hi',
			2 => 'Hello',
		]
	],
];

but should be:

$arr = [
	1 => [
		'id' => 1,
		'families' => [
			1 => [
				'id' => 3,
				'families' => [
					0 => [
						'id' => 2,
						'products' => [
							1 => 'Arnold'
						],
					],
				]
			],
		]
	],
	2 => [
		0 => [
			'id' => 2,
			'products' => [
				1 => 'John Doe'
			],
		],
	],
	3 => [
		'id' => 1,
		'products' => [
			1 => 'Hi',
			2 => 'Hello',
		]
	],
];

Just a simple change:
$hasProducts[$key]['families'] = $familiesWithProducts[0];

array(2) {
  [2]=>
  array(1) {
    ["families"]=>
    array(2) {
      ["id"]=>
      int(2)
      ["products"]=>
      array(1) {
        [1]=>
        string(8) "John Doe"
      }
    }
  }
  [3]=>
  array(2) {
    ["id"]=>
    int(1)
    ["products"]=>
    array(2) {
      [1]=>
      string(2) "Hi"
      [2]=>
      string(5) "Hello"
    }
  }
}

I edited the previous answer a bit, surprisingly, this was quite an easy problem to solve, the bigger issue now is that our deeply nested arrays get lost along with their parents (there’s an example in the edited answer above)

I don’t understand what you mean.

BTW., with my last example:

You’re going to need:

    if (isset($district['id']) && isset($hasProducts[$key])){
        $hasProducts[$key]['id'] = $district['id'];
    }

at end of foreach. It allows the id to be added only if there has been keys added previously.

I apologise, I should’ve made a new reply and refrained from editing the previous one, it only created a chaos.

I think I’ve finally got it!

What I meant was…

You can try this array:


$districts = [
	1 => [
		'id' => 1,
		'families' => [
			0 => [
				'id' => 2
			],
			1 => [
				'id' => 3,
				'families' => [
					0 => [
						'id' => 2,
						'products' => [
							1 => 'Arnold'
						],
					],
				]
			],
		]
	],
	2 => [
		'id' => 1,
		'families' => [
			0 => [
				'id' => 2,
				'products' => [
					1 => 'John Doe'
				],
			],
			1 => [
				'id' => 3,
				'products' => [],
			],
		]
	],
	3 => [
		'id' => 1,
		'products' => [
			1 => 'Hi',
			2 => 'Hello',
		]
	],
	4 => [
		'id' => 1,
		'families' => [
			0 => [
				'id' => 2
			],
			1 => [
				'id' => 3
			],
		]
	],
];

And run this code:

// makes new array, note: not really necessary
$hasProducts = array();

// cycling through each district (your termed it arr)
foreach ($districts as $key => $district){

	// don't even bother with rest of code if either key is missing
	if (!isset($district['products']) && !isset($district['families'])) continue;

	// if products key exists on (arr) || (districts) copy it into new array
	if (isset($district['products'])){
		$hasProducts[$key] = $districts[$key];
	}

	// if the key families exists, filter out any families with no products
	if (isset($district['families'])){


		// uncomment me start ->
		/*$familiesWithProducts = array_filter($district['families'], $filter = function ($x) use (&$filter) {
			return is_array($x) && (!empty($x['products']) || array_filter($x, $filter));
		});*/
		// uncomment me end <-

		$familiesWithProducts = array_filter($district['families'], function($family){
			return !empty($family['products']);
		});

		// if a family with products exists,. add it to array
		if (!empty($familiesWithProducts)){
			$hasProducts[$key] = $districts[$key];
			$hasProducts[$key]['families'] = $familiesWithProducts;
		}
	}
}

The result is:

(
    [2] => Array
        (
            [id] => 1
            [families] => Array
                (
                    [0] => Array
                        (
                            [id] => 2
                            [products] => Array
                                (
                                    [1] => John Doe
                                )

                        )

                )

        )

    [3] => Array
        (
            [id] => 1
            [products] => Array
                (
                    [1] => Hi
                    [2] => Hello
                )

        )

)

But should be(when you uncomment the condition above and comment out the few lines below):

(
    [1] => Array
        (
            [id] => 1
            [families] => Array
                (
                    [1] => Array
                        (
                            [id] => 3
                            [families] => Array
                                (
                                    [0] => Array
                                        (
                                            [id] => 2
                                            [products] => Array
                                                (
                                                    [1] => Arnold
                                                )

                                        )

                                )

                        )

                )

        )

    [2] => Array
        (
            [id] => 1
            [families] => Array
                (
                    [0] => Array
                        (
                            [id] => 2
                            [products] => Array
                                (
                                    [1] => John Doe
                                )

                        )

                )

        )

    [3] => Array
        (
            [id] => 1
            [products] => Array
                (
                    [1] => Hi
                    [2] => Hello
                )

        )

)

You completely changed the structure of your array.

You went from:

'families' => [
    'id'       => $id,
    'products' => array(...);
];

to

'families' => [
     0 => [
             'id'       => $id,
             'products' => array(...);
     ],
     1 => [
             'id'       => $id,
             'products' => array(...);
     ],
];

I’m not interested in retyping this several times to get your final desired output. If you can critically think of what the boundaries are then, I can help you.

I just noticed you also have ‘families’ => ‘families’ => ‘products’

Right now it appears you don’t have a fixed depth to your arrays. So,. you’re likely going to need a while loop or recursion to recursively check each family array.

I don’t want you to retype the piece of code several times. The aforementioned array was supposed to be an example, not a fixed one. It looks like we haven’t understoond one another. In the first post, where I said:

I need to keep all ancestors and descendants where there is at least one item in the key “products”, all other arrays should be unset.

I meant that I didn’t know what the depth of the array was, also the items in the array will change based on certain conditions i.e. if a product changes from active → inactive, it won’t be included.

The thing is that [products] are assigned to families based on familie’s ids and families are created dynamically. So, you can end up with a bunch of nested families which may not have any products in them, thus must be removed.