0

I need to remove an element form a deeply nested array of unknown structure (i.e. I do not know what the key sequence would be to address the element in order to unset it). The element I am removing however does have a consistent structure (stdObject), so I can search the entire multidimensional array to find it, but then it must be removed. Thoughts on how to accomplish this?

EDIT: This is the function I have right now trying to achieve this.

function _subqueue_filter_reference(&$where)
{
    foreach ($where as $key => $value) {
        if (is_array($value))
        {
            foreach ($value as $filter_key => $filter)
            {
                if (isset($filter['field']) && is_string($filter['field']) && $filter['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
                {
                    unset($value[$filter_key]);
                    return TRUE;
                }
            }
            return _subqueue_filter_reference($value);
        }
    }
    return FALSE;
}

EDIT #2: Snipped of array structure from var_dump.

array (size=1)
  1 => 
    array (size=3)
      'conditions' => 
        array (size=5)
          0 => 
            array (size=3)
              ...
          1 => 
            array (size=3)
              ...
          2 => 
            array (size=3)
              ...
          3 => 
            array (size=3)
              ...
          4 => 
            array (size=3)
              ...
      'args' => 
        array (size=0)
          empty
      'type' => string 'AND' (length=3)

...so assuming that this entire structure is assigned to $array, the element I need to remove is $array[1]['conditions'][4] where that target is an array with three fields:

  • field
  • value
  • operator

...all of which are string values.

Lester Peabody
  • 1,868
  • 3
  • 20
  • 42
  • http://stackoverflow.com/questions/369602/delete-an-element-from-an-array?rq=1 – ಠ_ಠ Aug 20 '13 at 13:10
  • I've already read this and it does not answer my question. Using `unset` would be easy if I knew the exact key sequence to the deeply nested element, but I don't. – Lester Peabody Aug 20 '13 at 13:12
  • You know that you have to search it. So when you found it use unset. – Tobias Golbs Aug 20 '13 at 13:13
  • http://php.net/manual/en/function.array-search.php – ಠ_ಠ Aug 20 '13 at 13:14
  • Maybe I don't understand. Search and remove...right? – ಠ_ಠ Aug 20 '13 at 13:14
  • The issue I'm having is that the array can be arbitrarily deep, so `array_search` won't work. I've tried creating a recurisve helper function to dig down, and I do end up finding the element, but if I unset that element, even if I pass the array in by reference, the result is the array is unmodified at the top level. Maybe I'm doing something wrong within that function...? – Lester Peabody Aug 20 '13 at 13:17
  • I'll dig around more, I'm sure this has been done before so it's just a matter of time before I figure it out/find an answer for it, but I figured I'd post it up on Stack for future reference. – Lester Peabody Aug 20 '13 at 13:20
  • I would assume that the recursive function doesn't truly pass the array by reference, can you show me the function? – ಠ_ಠ Aug 20 '13 at 13:25
  • I added the function I'm using/writing. – Lester Peabody Aug 20 '13 at 13:29

3 Answers3

2

This is just a cursor problem.

function recursive_unset(&$array)
{
    foreach ($array as $key => &$value) # See the added & here.
    {
        if(is_array($value))
        {
            if(isset($value['field']) && $value['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
            {
                unset($array[$key]);
            }
            recursive_unset($value);
        }
    }
}

Notes : you don't need to use is_string here, you can just make the comparison as you're comparing to a string and the value exists.

Don't use return unless you're sure there is only one occurrence of your value.

Edit :

Here is a complete example with an array similar to what you showed :

$test = array (
        1 => array (
                'conditions' =>
                array (
                        0 => array ('field' => 'dont_care1', 'value' => 'test', 'operator' => 'whatever'),
                        1 => array ('field' => 'dont_care2', 'value' => 'test', 'operator' => 'whatever'),
                        2 => array ('field' => 'nodequeue_nodes_node__nodequeue_subqueue.reference', 'value' => 'test', 'operator' => 'whatever'),
                        3 => array ('field' => 'dont_care3', 'value' => 'test', 'operator' => 'whatever')
                ),
        'args' => array (),
        'type' => 'AND'
));

var_dump($test);

function recursive_unset(&$array)
{
    foreach ($array as $key => &$value)
    {
        if(is_array($value))
        {
            if(isset($value['field']) && $value['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
            {
                unset($array[$key]);
            }
            recursive_unset($value);
        }
    }
}

recursive_unset($test);

var_dump($test);
Kethryweryn
  • 639
  • 4
  • 11
  • Just tested this code on a 3 dimensions array and it worked. It wouldn't work if the 'field' was on the first level of the array though (won't delete the array itself). – Kethryweryn Aug 20 '13 at 14:50
  • I added a sample of the array structure that I am searching. – Lester Peabody Aug 20 '13 at 15:12
  • Edited to show complete code. Still works for me with the sampled array. Maybe show your implementation of the code you made ? Will be a better solution than an EVAL. – Kethryweryn Aug 20 '13 at 15:27
0

One way to solve this was to extend your recursive function with a second parameter:

function _subqueue_filter_reference(&$where, $keyPath = array())

You'd still do the initial call the same way, but the internal call to itself would be this:

return _subqueue_filter_reference($value, array_merge($keyPath, array($key)));

This would provide you with the full path of keys to reach the current part of the array in the $keyPath variable. You can then use this in your unset. If you're feeling really dirty, you might even use eval for this as a valid shortcut, since the source of the input you'd give it would be fully within your control.

Edit: On another note, it may not be a good idea to delete items from the array while you're looping over it. I'm not sure how a foreach compiles but if you get weird errors you may want to separate your finding logic from the deleting logic.

0

I have arrived at a solution that is a spin-off of the function found at http://www.php.net/manual/en/function.array-search.php#79535 (array_search documentation).

Code:

function _subqueue_filter_reference($haystack,&$tree=array(),$index="")
{
    // dpm($haystack);
    if (is_array($haystack))
    {

        $result = array();

        if (count($tree)==0)
        {
            $tree = array() + $haystack;
        }

        foreach($haystack as $k=>$current)
        {
            if (is_array($current))
            {
                if (isset($current['field']) && is_string($current['field']) && $current['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
                {
                    eval("unset(\$tree{$index}[{$k}]);"); // unset all elements = empty array
                }
                _subqueue_filter_reference($current,$tree,$index."[$k]");
            }
        } 
    } 
    return $tree;
}

I hate having to use eval as it SCREAMS of a giant, gaping security hole, but it's pretty secure and the values being called in eval are generated explicitly by Drupal core and Views. I'm okay with using it for now.

Anyway, when I return the tree I simply replace the old array with the newly returned tree array. Works like a charm.

Lester Peabody
  • 1,868
  • 3
  • 20
  • 42