aviator/array-map-keys
A small PHP utility for transforming array keys throughout nested arrays. Pass a callback to map every key, including helpers for common conversions like snake_case to camelCase. Handy for normalizing config arrays or API payloads before processing.
This package solves one narrow problem: array_map() hands your callback the value but not the key, so any transformation where the output depends on the key (renaming, prefixing, filtering by key, building id => label maps) forces you into foreach. The package adds a key-aware array_map.
1. Install and confirm the surface area first. The library is a single-purpose utility, so the whole API is visible in under a minute:
composer require aviator/array-map-keys
Then open vendor/aviator/array-map-keys/composer.json. Look at autoload:
files entry, the package registers a global helper function you can call anywhere.Read vendor/aviator/array-map-keys/src/ — for a package this small (last release 2017, 3 stars, 0 dependents) you should never adopt it without reading the source, because there is no community usage to fall back on.
2. Smoke-test the exact contract in Tinker. Two things vary between implementations and both will bite you: argument order (key, value vs value, key) and return shape (a bare value that replaces the value with the key preserved, vs a [key => value] pair that rebuilds the array).
php artisan tinker
>>> array_map_keys(fn ($key, $value) => [$key => $value * 2], ['a' => 1, 'b' => 2]);
Whatever that prints is your contract. Write it down.
3. First real use case. Building lookup maps from keyed data you already hold — config arrays, cached payloads, pluck() results, or DTO hydration — where a Collection would be overkill or isn't available yet:
$roles = ['admin' => 1, 'editor' => 2, 'viewer' => 3];
// Invert: permission level => role name
$byLevel = array_map_keys(fn ($name, $level) => [$level => $name], $roles);
// [1 => 'admin', 2 => 'editor', 3 => 'viewer']
Normalize inbound payloads before validation. Form/API payloads often arrive as ['field_name' => ...] lists or keyed-by-index arrays. Key-aware mapping is a clean place to convert key casing or flatten a nested keyed structure:
$camel = array_map_keys(
fn ($key, $value) => [Str::camel($key) => $value],
$request->validated()
);
Namespace cache keys. Prefixing every key in a config-derived array is a one-liner:
$cacheKeys = array_map_keys(
fn ($key, $value) => ["reports.{$key}" => $value],
config('reports.sections')
);
Wrap it behind your own helper. Do not sprinkle a 2017 third-party global function through your codebase. Put the call site behind something you own:
// app/Support/Arr.php
public static function remapKeys(array $array, callable $callback): array
{
return array_map_keys($callback, $array); // the only call site in the app
}
Now replacing it with Arr::mapWithKeys() or a native foreach is a three-line change, and your IDE/PHPStan only needs one stub.
Use it on plain arrays, not Eloquent collections. On collections you already have mapWithKeys() (Laravel 5.4+) and chaining. This package earns its place in the gap before hydration: config, JSON from cache, package-provided arrays, DTOs, and code that deliberately avoids the container.
Pair it with array_filter for key-driven pruning. array_filter($arr, ..., ARRAY_FILTER_USE_KEY) handles deletion; this handles reshaping. Composing the two is clearer than a single foreach doing both:
$public = array_filter($settings, fn ($k) => ! Str::startsWith($k, '_'), ARRAY_FILTER_USE_KEY);
$prefixed = array_map_keys(fn ($k, $v) => ["app.{$k}" => $v], $public);
Verify argument order in a test, not in your head. fn ($key, $value) vs fn ($value, $key) is the single most likely source of a silently wrong result — PHP won't complain, you'll just get transposed data. Cover one call with a unit test asserting the exact output with assertSame.
Duplicate output keys overwrite silently. If your callback maps two inputs to the same output key, the last one wins with no warning. If collisions are possible, validate the input's cardinality against the output before trusting the result.
PHP coerces numeric-string keys. '1' becomes int(1), but '01' stays a string. Any key-building logic producing zero-padded or numeric keys will produce a mixed-type array, which then surprises json_encode, ksort, and strict comparisons. Cast explicitly in the callback if type matters.
Stale dependency hygiene. Last tagged release is 2017. The code is almost certainly a few lines and will run fine on modern PHP, but the package is unmaintained by definition: it won't get a PHP 8.x compatibility fix. Pin it, run composer audit, and if you keep it long-term, vendor the function into app/Support with a replace entry in composer.json so the dependency graph stops carrying dead weight.
Static analysis will flag the global function. PHPStan and Psalm don't know it exists. Add a stub file (or a @method-free plain function declaration) to your phpstan.neon scanFiles/stubFiles rather than sprinkling @phpstan-ignore-next-line.
Check for function-name collisions on autoload. Global-function packages register via files autoload, which loads eagerly. If another package defines the same name you get a fatal error at boot, not a graceful failure. composer dump-autoload -o plus a php -r "var_dump(function_exists('array_map_keys'));" will tell you quickly.
Extension point is the callback, not the package. Everything useful happens in your closure. Keep callbacks pure and single-purpose; when a callback needs the container, a repository, or a model, that's the signal to stop using a mapping utility and write an explicit loop or a dedicated transformer class.
Debugging: dd(array_map_keys(...)) mid-pipeline, and diff the result against the input with array_diff_key/array_diff_assoc to see exactly which keys moved, vanished, or collided.
How can I help you explore Laravel packages today?