Table of Contents
You’re reading a method and hit something like this:
$results = $categories->map(function ($category) use ($config) {
return $category->items->filter(function ($item) use ($config) {
return $item->variants->map(function ($variant) use ($config, $item) {
$basePrice = $variant->price * $config->multiplier;
$discount = $item->hasDiscount() ? $basePrice * $item->discountRate() : 0;
return [
'sku' => $variant->sku,
'final_price' => $basePrice - $discount,
'label' => "{$item->name} — {$variant->size}",
];
})->filter(fn ($v) => $v['final_price'] > 0);
})->flatten(1);
})->flatten(1);
Three levels deep. Four use imports. By the time you reach the inner logic, you’ve lost the context of the outer layers. This is the closure nesting trap.
Extract and Name
Pull the inner closure into a private method with a name that describes what it does:
$results = $categories->map(function ($category) use ($config) {
return $category->items
->flatMap(fn ($item) => $this->buildVariantPrices($item, $config))
->filter(fn ($v) => $v['final_price'] > 0);
})->flatten(1);
private function buildVariantPrices(Item $item, PricingConfig $config): Collection
{
return $item->variants->map(function ($variant) use ($config, $item) {
$basePrice = $variant->price * $config->multiplier;
$discount = $item->hasDiscount() ? $basePrice * $item->discountRate() : 0;
return [
'sku' => $variant->sku,
'final_price' => $basePrice - $discount,
'label' => "{$item->name} — {$variant->size}",
];
});
}
Why This Works
The parent chain becomes scannable. You can read the top-level flow — map categories, build variant prices, filter positives — without parsing the inner logic. The method name tells you what happens; the method body tells you how.
Type hints become possible. The extracted method can declare its parameter and return types. The closure couldn’t.
Testing gets easier. You can test buildVariantPrices() directly with a mock item and config, without setting up the full category→item→variant hierarchy.
The Rule of Thumb
If a closure:
- Has more than 2
useimports - Is nested inside another closure
- Contains more than 5 lines of logic
…extract it into a named private method. The method name acts as documentation, and the signature acts as a contract.
The takeaway: Closures are great for simple transformations. But when they nest, they become anonymous complexity. Give them a name, and your code reads like an outline instead of a puzzle.
Leave a Reply