1

I am looking for a solution in php as mentioned in the accepted answer of this question:

javascript - return parent with only child that matches given search string in array of objects with nested object

Please find below code:

<?php
    $items = array( 
        'tableData' => array
        (
            array
            (
                'booking_name' => 'abc/xyz/123',
                'pdg' => 'assure',                    
                'user_area' => 'es st1',
                'release' => 'oss72',
                'start_date' => '2017-06-20 00:00:00',
                'end_date' => '2017-06-23 00:00:00',
                'asset_info' => array
                    (
                        array
                            (
                                'status' => 10,
                                'manufacturer' => 'Oracle',
                                'model' => 'HP BL460C GEN8',
                                'hardware_color' => '#0066b3',
                            ),
                        array
                            (
                                'status' => 11,
                                'manufacturer' => 'HP',
                                'model' => 'HP BL460C GEN81',
                                'hardware_color' => '#0066b3',
                            )

                    ),

                'full_name' => 'Valay Desai',
                'email_address' => 'valay@xyz.com',
            ),

            array
            (
                'booking_name' => 'abc/xyz/123',
                'pdg' => 'enm',                    
                'user_area' => 'es st',
                'release' => 'oss72',
                'start_date' => '2017-06-20 00:00:00',
                'end_date' => '2017-06-23 00:00:00',
                'asset_info' => array
                    (
                        array
                            (
                                'status' => 10,
                                'manufacturer' => 'HP',
                                'model' => 'HP BL460C GEN8',
                                'hardware_color' => '#0066b3',
                            )

                    ),

                'full_name' => 'Valay Desai',
                'email_address' => 'valay@xyz.com',
            )
        )
    );

function getParentStackComplete($child, $stack) {
    $return = array();
    foreach ($stack as $k => $v) {
        if (is_array($v)) {
            // If the current element of the array is an array, recurse it 
            // and capture the return stack
            $stack = getParentStackComplete($child, $v);

            // If the return stack is an array, add it to the return
            if (is_array($stack) && !empty($stack)) {
                $return[] = $v;
            }
        } else {
            // Since we are not on an array, compare directly
            if(strpos($v, $child) !== false){               
                // And if we match, stack it and return it
                $return[] = $v;
            }
        }
    }

    // Return the stack
    return empty($return) ? false: $return;
}


echo "<pre>";
print_r(getParentStackComplete('Oracle', $items['tableData']));
echo "</pre>";


?>

This code works fine. I found the function getParentStackComplete online, modified it to return the whole matching element. It search the array recursively and return matching items.

For example, as given in the code, If I search for a string 'Oracle', it should return an array with one item which has only one child(matching element) in asset_info. The output I am looking for is:

Array
(
    [0] => Array
        (
            [booking_name] => abc/xyz/123
            [pdg] => assure
            [user_area] => es st1
            [release] => oss72
            [start_date] => 2017-06-20 00:00:00
            [end_date] => 2017-06-23 00:00:00
            [asset_info] => Array
                (
                    [0] => Array
                        (
                            [status] => 10
                            [manufacturer] => Oracle
                            [model] => HP BL460C GEN8
                            [hardware_color] => #0066b3
                        )
                )

            [full_name] => Valay Desai
            [email_address] => valay@xyz.com
        )

)

If I search for a string HP BL460C GEN8, it should return as below:

Array
(
    [0] => Array
        (
            [booking_name] => abc/xyz/123
            [pdg] => assure
            [user_area] => es st1
            [release] => oss72
            [start_date] => 2017-06-20 00:00:00
            [end_date] => 2017-06-23 00:00:00
            [asset_info] => Array
                (
                    [0] => Array
                        (
                            [status] => 10
                            [manufacturer] => Oracle
                            [model] => HP BL460C GEN8
                            [hardware_color] => #0066b3
                        )
                )

            [full_name] => Valay Desai
            [email_address] => valay@xyz.com
        )
       [1] => Array
       (
          'booking_name' => 'abc/xyz/123',
                'pdg' => 'enm',                    
                'user_area' => 'es st',
                'release' => 'oss72',
                'start_date' => '2017-06-20 00:00:00',
                'end_date' => '2017-06-23 00:00:00',
                'asset_info' => array
                    (
                        array
                            (
                                'status' => 10,
                                'manufacturer' => 'HP',
                                'model' => 'HP BL460C GEN8',
                                'hardware_color' => '#0066b3',
                            )

                    ),

                'full_name' => 'Valay Desai',
                'email_address' => 'valay@xyz.com'
       )

)

How do I return matching child with parent in nested array search ?

Valay
  • 1,991
  • 2
  • 43
  • 98

5 Answers5

4

Try this code.

function getParentStackComplete( $search, $stack ){

    $results = array();

    foreach( $stack as $item ){

        if( is_array( $item ) ){

            if( array_filter($item, function($var) use ($search) { return ( !is_array( $var ) )? stristr( $var, $search ): false; } ) ){
                //echo 'test';
                $results[] = $item;
                continue;
            }else if( array_key_exists('asset_info', $item) ){
                $find_assets = array();
                foreach( $item['asset_info'] as $k=>$v ){
                    //echo 'abc ';

                    if( is_array( $v ) && array_filter($v, function($var) use ($search) { return stristr($var, $search); }) ){
                        $find_assets[] = $v;
                    }
                }
                if( count( $find_assets ) ){
                    $temp = $item;
                    $temp['asset_info'] = $find_assets;
                    $results[] = $temp;
                }
            }
        }
    }

    return $results;
}
Manish
  • 122
  • 2
3

To deeply search only the leaf nodes, this is straight forward with recursive iteration via RecursiveIterator, its RecursiveIteratorIterator handles leaf-only traversal via RecursiveArrayIterator.

To make this visible here a small search example on your example data:

$iterator = new RecursiveArrayIterator($items['tableData']); # 1.
$leafs = new RecursiveIteratorIterator($iterator); # 2.
$search = new RegexIterator($leafs, sprintf('~^%s$~', preg_quote('HP BL460C GEN8', '~'))); # 3.
foreach ($search as $value) { # 4.
    var_dump($value);
}

It does

  1. Decorate the array to search through as a RecursiveArrayIterator.
  2. Decorate the array iterator for leafs-only traversal via RecursiveIteratorIterator.
  3. Apply the search (again as a decorator) on all the leaf values.
  4. The rest is to foreach the search and output the values for demonstration.

And will output:

string(14) "HP BL460C GEN8"
string(14) "HP BL460C GEN8"

The search is effectively set-up in three lines of code.

And that's not all, as inside the foreach we still have the context of the decorated iteration, you can access not only the current value, but also that three levels up, the parent you want to return:

foreach ($search as $key => $value) {
    $parentLevel = 0; # or for relative access: $leafs->getDepth() - 3
    $parent = $leafs->getSubIterator($parentLevel)->current();
    var_dump($parent);
}

This will output all parent objects that matched the search.

That might answer your question already, so let's show the example in full:

$search = function (array $array, string $term) {
    $iterator = new RecursiveArrayIterator($array);
    $leafs = new RecursiveIteratorIterator($iterator);
    $search = new RegexIterator($leafs, sprintf('~^%s$~', preg_quote($term, '~')));
    foreach ($search as $value) {
        $parent = $leafs->getSubIterator(0)->current();
        yield $parent;
    }
};

$matches = $search($items['tableData'], 'HP BL460C GEN8');
foreach ($matches as $index => $match) {
    echo $index + 1, ': ';
    print_r($match);
}

And it's output:

1: Array
(
    [booking_name] => abc/xyz/123
    [pdg] => assure
    [user_area] => es st1
    [release] => oss72
    [start_date] => 2017-06-20 00:00:00
    [end_date] => 2017-06-23 00:00:00
    [asset_info] => Array
        (
            [0] => Array
                (
                    [status] => 10
                    [manufacturer] => Oracle
                    [model] => HP BL460C GEN8
                    [hardware_color] => #0066b3
                )

            [1] => Array
                (
                    [status] => 11
                    [manufacturer] => HP
                    [model] => HP BL460C GEN81
                    [hardware_color] => #0066b3
                )

        )

    [full_name] => Valay Desai
    [email_address] => valay@xyz.com
)
2: Array
(
    [booking_name] => abc/xyz/123
    [pdg] => enm
    [user_area] => es st
    [release] => oss72
    [start_date] => 2017-06-20 00:00:00
    [end_date] => 2017-06-23 00:00:00
    [asset_info] => Array
        (
            [0] => Array
                (
                    [status] => 10
                    [manufacturer] => HP
                    [model] => HP BL460C GEN8
                    [hardware_color] => #0066b3
                )

        )

    [full_name] => Valay Desai
    [email_address] => valay@xyz.com
)

But what if you might want to reduce the parents asset_info array to only contain those matches and not only all parents containing a match. If so this would require to create a result array which only contains those entries in asset_info entries that were a match. This requires to keep track of matched parents so that matches asset_infos can be added to their result.

As this requires to process all matched assets of the same parent and then provide that parent only with these matches. So matches will become grouped in their parent, therefore this is sort of an aggregation function and has therefore a bit more things to manage as it needs to keep track if the parent has all matches already:

$search = function (array $array, string $term) {
    $iterator = new RecursiveArrayIterator($array);
    $leafs = new RecursiveIteratorIterator($iterator);
    /* @var $search RecursiveIteratorIterator|RegexIterator - $search is a decorator of that type */
    $search = new RegexIterator($leafs, sprintf('~^%s$~', preg_quote($term, '~')));

    # initialize
    $match = $lastId = null;

    foreach ($search as $key => $value) {
        $parentId = $search->getSubIterator(0)->key();
        if ($lastId !== $parentId && $match) {
            yield $match;
            $match = null;
        }
        $lastId = $parentId;

        if (empty($match)) {
            # set match w/o asset_info as yet not matched
            $match = $search->getSubIterator(0)->current();
            $match['asset_info'] = [];
        }

        # add matched asset into the matched asset_info
        $match['asset_info'][] = $search->getSubIterator(2)->current();
    }

    $match && yield $match;

};

$matches = $search($items['tableData'], 'HP BL460C GEN8');
foreach ($matches as $index => $match) {
    echo $index + 1, ': ';
    print_r($match);
}

The output gives in your case:

1: Array
(
    [booking_name] => abc/xyz/123
    [pdg] => assure
    [user_area] => es st1
    [release] => oss72
    [start_date] => 2017-06-20 00:00:00
    [end_date] => 2017-06-23 00:00:00
    [asset_info] => Array
        (
            [0] => Array
                (
                    [status] => 10
                    [manufacturer] => Oracle
                    [model] => HP BL460C GEN8
                    [hardware_color] => #0066b3
                )

        )

    [full_name] => Valay Desai
    [email_address] => valay@xyz.com
)
2: Array
(
    [booking_name] => abc/xyz/123
    [pdg] => enm
    [user_area] => es st
    [release] => oss72
    [start_date] => 2017-06-20 00:00:00
    [end_date] => 2017-06-23 00:00:00
    [asset_info] => Array
        (
            [0] => Array
                (
                    [status] => 10
                    [manufacturer] => HP
                    [model] => HP BL460C GEN8
                    [hardware_color] => #0066b3
                )

        )

    [full_name] => Valay Desai
    [email_address] => valay@xyz.com
)

Mind the subtle differences in the first match on the number of entries in the asset_info, that is one instead of the earlier two.

hakre
  • 193,403
  • 52
  • 435
  • 836
  • thanks for the response. yes this works fine. But if I search for a value which is not inside an array, the function doesn't return anything. For example if I search for 'assure' or for 'enm'. FYI- I haven't used `RecursiveArrayIterator` before. – Valay Jul 03 '17 at 15:21
  • I am not able to search only in parent with this solution. – Valay Jul 04 '17 at 16:05
  • You can do so by adopting the search filter making use of the stack as shown on where to obtain the parent. The traversal and search can be fully tailored, I did read your question that you wanted to search the leafs so I demonstrated that. – hakre Jul 04 '17 at 17:37
1
<?php
    $items = array( 
        'tableData' => array
        (
            array
            (
                'booking_name' => 'abc/xyz/123',
                'pdg' => 'assure',                    
                'user_area' => 'es st1',
                'release' => 'oss72',
                'start_date' => '2017-06-20 00:00:00',
                'end_date' => '2017-06-23 00:00:00',
                'asset_info' => array
                    (
                        array
                            (
                                'status' => 10,
                                'manufacturer' => 'Oracle',
                                'model' => 'HP BL460C GEN8',
                                'hardware_color' => '#0066b3',
                            ),
                        array
                            (
                                'status' => 11,
                                'manufacturer' => 'HP',
                                'model' => 'HP BL460C GEN81',
                                'hardware_color' => '#0066b3',
                            )

                    ),

                'full_name' => 'Valay Desai',
                'email_address' => 'valay@xyz.com',
            ),

            array
            (
                'booking_name' => 'abc/xyz/123',
                'pdg' => 'enm',                    
                'user_area' => 'es st',
                'release' => 'oss72',
                'start_date' => '2017-06-20 00:00:00',
                'end_date' => '2017-06-23 00:00:00',
                'asset_info' => array
                    (
                        array
                            (
                                'status' => 10,
                                'manufacturer' => 'HP',
                                'model' => 'HP BL460C GEN8',
                                'hardware_color' => '#0066b3',
                            )

                    ),

                'full_name' => 'Valay Desai',
                'email_address' => 'valay@xyz.com',
            )
        )
    );

function getParentStackComplete($child, $stack) {
    $return = array();
    $k=0;   
    foreach ($stack as $k => $v) { 

        if (is_array($v)) {          


            if (is_array($stack) && !empty($stack) && $k==0) {
                unset($v['asset_info'][1]);
                $return = $v;              

            }
        } else {

            if(strpos($v, $child) !== false){    

                $return[] = $v;
            }
        }
        $k++;
    }    
    return empty($return) ? false: $return;
}


echo "<pre>";
print_r(getParentStackComplete('Oracle', $items['tableData']));
echo "</pre>";
harsh kumar
  • 165
  • 7
  • why did you remove recursive function call ? – Valay Jun 28 '17 at 16:14
  • Hi Valay this is an alternate method found to get result with recursive function we will not able to get result.if we comment recursive function to get correct output it's not wrong.... – harsh kumar Jun 29 '17 at 05:54
  • Hello Valay I was thinking we need second array but i was wrong ,You should try this code please it will give you correct data... you need to use unset($v['asset_info'][1]); in loop...see and check it out... – harsh kumar Jun 29 '17 at 08:31
  • Please try again this updated code and let me know if any issue you will get ? – harsh kumar Jun 29 '17 at 08:39
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/147906/discussion-between-valay-and-harsh-kumar). – Valay Jun 29 '17 at 08:53
1

I was looking to return a result based on an exact match so have modified Manish's answer below with a third parameter of strict. Passing true will return an exact match only.

public function getParentStackComplete($search, $stack, $strict = false)
{
    $results = array();

    foreach ($stack as $item) {

        if (is_array($item)) {

            if (array_filter($item, function ($var) use ($search) {
                return (!is_array($var)) ? stristr($var, $search) : false;
            })) {
                //echo 'test';
                $results[] = $item;

                if ($strict) {
                    if ($item['name'] === $search)
                        return $item;
                }

                continue;
            } else if (array_key_exists('asset_info', $item)) {
                $find_assets = array();
                foreach ($item['asset_info'] as $k => $v) {
                    //echo 'abc ';

                    if (is_array($v) && array_filter($v, function ($var) use ($search) {
                            return stristr($var, $search);
                        })) {
                        $find_assets[] = $v;
                    }
                }
                if (count($find_assets)) {
                    $temp = $item;
                    $temp['asset_info'] = $find_assets;
                    $results[] = $temp;
                }

            }
        }
    }

    return $results;
}
Friendly Code
  • 1,585
  • 1
  • 25
  • 41
-1

This should do the trick.

function getParentStackComplete($search, $stack)
{
    $results = [];
    foreach ($stack as $i => $item) {
        $results[] = $item;
        $cache = $item['asset_info'];
        $results[$i]['asset_info'] = [];
        $found = false;

        foreach ($cache as $asset) {
            if (array_search($search, $asset) !== false) {
                print('here' . "\n");
                $found = true;
                $results[$i]['asset_info'][] = $asset;
            }
        }

        if (!$found) {
            unset($results[$i]);
        }
    }
    return $results;
}

Edit: This assumes you're calling like this getParentStackComplete('Oracle', $items['tableData'])

Mike
  • 346
  • 2
  • 8
  • thanks for the response. But `asset_info` nested array is not mandatory. And the solution should be dynamic irrespective of key names. – Valay Jun 30 '17 at 18:20
  • What do you mean by _dynamic irrespective of key names_? Could you give an example? If you just mean you need to check the other keys, my solution is easily extended to not be hard coded. If you need the depth of the tree to be dynamic then you need to recursively traverse the tree. As far as not requiring an 'asset_info' key, just check if it exists before continuing. This isn't a site for people to code everything for you, it's to help you find your solution. – Mike Jun 30 '17 at 18:28
  • I think I know how SO works and I already have tried from whatever I know about it. Thanks for the solution you provided and for letting me know what to do and how to do on SO. – Valay Jun 30 '17 at 18:40