#development #laravel #php

I'm quite a fan of using PHP first class callables, but combining them with Laravel collections can be a bit tricky.

Imagine you have a collection with header names you want to convert to snake case.

1$headers = collect(["First Name", "Last Name", "Zip Code", "City"]);

With first class callables, you would do something like this:

1$headers->map(Str::snake(...);

Very concise if you ask me, but it won't give you the expected result. What you get is:

1$headers->map(Str::snake(...))
2// Illuminate\Support\Collection {#9590
3//   all: [
4//     "firsname",
5//     "lasname",
6//     "zicode",
7//     "city",
8//   ],
9// }

What you would have expected is (when you are not using first class callables):

1$headers->map(fn ($header) => Str::snake($header));
2// Illuminate\Support\Collection {#9583
3//   all: [
4//     "first_name",
5//     "last_name",
6//     "zip_code",
7//     "city",
8//   ],
9// }

What is happening here and how can this be explained? First, let's take a look at the signature of the Str::snake method:

1static string snake(string $value, string $delimiter = '_')

As you can see, the Str::snake method expects a string as the first argument and an optional delimiter as the second argument.

If we then look at the signature of the map callback method, you'll see that for each iteration, the first argument will be the item from the collection, the second one is the key of the item in the collection. This translates to the following being executed:

1$headers->map(
2    fn (string $header, int $key): string => Str::snake($header, $key)
3);

As you can see, it's trying to use the key as the delimiter, which is not what we want.

So, for these type of situations, you cannot use the first class callables and should use a regular closure instead.