Search code examples
phprecursionswitch-statementcomparison

Iterating over an array recursively causes invalid results in callback function


I'm trying to iterate over every element of an array recursively to get the values where the key is equal to page_id or content_id.

The problem is that in callback function, in a switch statement I get completely weird behaviour, where case is matched but value is different.

This was driving me nuts for quite some time. I even tried to use strict comparison like if($key === 'component_id') print $key; die();. I'm expecting to get 'component_id' as the output but get '0'. How is that possible?

While writing this question, I also noticed this happens when there is indexed array at some point, but I'm not sure if that is the problem.

Here is an example code from full solution.

Thanks.

<?php

class Arr
{
    public static function map($array, $callback, $recursive = false)
    {
        foreach ($array as $key => $value) {
            $array[$key] = $callback($key, $value);
            if($recursive && is_array($array[$key])) {
                $array[$key] = static::map($array[$key], $callback, true);
            }
        }
        return $array;
    }
}

$data = [
    'id' => 12,
    'data' => [
        'terms' => [
            [
                'href' => null,
                'icon' => 'lock',
                'target' => '_blank'
            ],
            [
                'href' => 'http://example.com',
                'icon' => 'lock',
                'target' => '_blank'
            ]
        ],
        'license_info' => [
            'content_id' => 6
        ]
    ]
];

$contents = [];
$pages = [];

Arr::map($data, function ($key, $value) use (&$contents, &$pages) {
    switch ($key) {
        case 'content_id':
            print $key; die(); // 0  ?????
            $contents[] = $value;
            break;
        case 'page_id':
            $pages[] = $value;
            break;
    }

    return $value;
}, true);

I expected the output to be component_id, but I got 0.

Also I know I could use array_walk or array_walk_recursive, but I prefer this approach as it's more elegant and readable in my opinion.


Solution

  • Your code looks ok. The problem is that 0 == 'content_id' is True. And switch in PHP is using loose (==) comparison. And because in terms you have two values without string keys, they are automatically indexed starting at 0. So what you get is not when your function finds

    'content_id' => 6
    

    but when it finds

    /* 0 => */
    [
       'href' => null,
       'icon' => 'lock',
       'target' => '_blank'
    ],
    

    EDIT: Bottom line is - you have to use if with strict comparison === in this case (or use string keys everywhere).