Why does array_map() with null as callback create an "array of arrays"?
Asked Answered
I

2

9

Today I learned about a special case of array_map() in PHP, which is mentioned as a side note in the documentation:

Example #4 Creating an array of arrays

<?php
$a = array(1, 2, 3, 4, 5);
$b = array("one", "two", "three", "four", "five");
$c = array("uno", "dos", "tres", "cuatro", "cinco");

$d = array_map(null, $a, $b, $c);
print_r($d);
?>

The above example will output:

Array
(
    [0] => Array
        (
            [0] => 1
            [1] => one
            [2] => uno
        )

    [1] => Array
        (
            [0] => 2
            [1] => two
            [2] => dos
        )

    [2] => Array
        (
            [0] => 3
            [1] => three
            [2] => tres
        )

    [3] => Array
        (
            [0] => 4
            [1] => four
            [2] => cuatro
        )

    [4] => Array
        (
            [0] => 5
            [1] => five
            [2] => cinco
        )

)

If the array argument contains string keys then the returned array will contain string keys if and only if exactly one array is passed. If more than one argument is passed then the returned array always has integer keys.

(try it out)

But that's it. No more explanation. I understand, that this does the same as

$d = array_map(function() { return func_get_args(); }, $a, $b, $c);

But why would anybody want or expect this as default behavior? Is there a technical reason why it works like that, like a side effect from the implemtation? Or was this just a random "let's make this function do one more thing" decision (looking at you, array_multisort())?

Indissoluble answered 23/7, 2015 at 16:0 Comment(3)
I guess that's just how it is implemented, also because they have documented it it's probably not a "side effect". Also it's super convenient to rotate an array with this?!Sexcentenary
Your question could also just have been: Why is it called array_map() and not call_function_for_each_array_element() ? <- Only the developer who implemented it could tell you that. But why would anybody want or expect this as default behavior? Why do you expect the first parameter to be the callback as default behaviour ?Sexcentenary
Because it's awesome!Coriolanus
W
5

This appears to be a special case in _array_map_, but it's only documented in that example. NULL is not normally allowed as a callback (if you try to use it with call_user_func() it reports an error), but it's allowed in _array_map()_. It treats NULL as meaning that it should simply create an array of the arguments.

This is useful because array is also not valid as a callback, because it's language construct, not a function. So you can't write:

$d = array_map('array', $a, $b, $c);
Winona answered 24/7, 2015 at 16:16 Comment(1)
TBH, the idea, that null stands for array as "callback" didn't occur to me, but this is a reasoning that somehow makes sense.Indissoluble
T
3

array_map(null, ...[arrays]) performs "transposition". Some people refer to this re-orientation as a "diagonal flip", but it functionally translates into converting columns of data into rows of data.

Even in the most recent version of PHP (8.1 as of the time of this post), this unintuitive native technique is still only suitable for transposing numerically-keyed arrays of numerically-keyed arrays SO LONG AS there is more than one row (subarray).

For clarity, the implementation of the spread operator in array_map(null, ...[$a, $b, $c]) is fundamentally the same as array_map(null, $a, $b, $c).

Developers who use this technique to prepare data for, say, a graphical or tabular representation, love the concise (albeit cryptic) syntax -- it is much sexier than spelling out two nested foreach loops.

What these developers might be surprised to learn is that when there aren't multiple rows of data to be transposed, the original 2D array is REDUCED to a 1D array. In this scenario, the null parameter version is not identical to the func_get_args() version.

Code: (Demo)

$array = [
    [1 => 'one', 2 => 'two', 3 => 'three']
];
var_export(array_map(fn() => func_get_args(), ...$array));
echo "\n---\n";
var_export(array_map(null, ...$array));

Output:

array (
  1 => 
  array (
    0 => 'one',
  ),
  2 => 
  array (
    0 => 'two',
  ),
  3 => 
  array (
    0 => 'three',
  ),
)
---
array (
  1 => 'one',
  2 => 'two',
  3 => 'three',
)

In summary, I cannot speak on behalf of language developers as to why this array_map() technique was designed this way, but I would like to take this moment to warn developers to not be seduced by its charm. It will destroy the associative relationships between numeric keys and their values and it may flatten unexpectedly your output array. In other words, make sure that your data is fit for the technique because the technique is not fit for all data.

Thereabouts answered 21/4, 2022 at 7:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.