Skip to content
Carl Victor Fontanos
Carl Victor Fontanos

Carl Victor Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

strlen(...) Is Valid PHP Now: First-Class Callables

C
Carlo Fontanos
· 2 min read

For twenty years, passing a function in PHP meant passing its name as a string - 'strtoupper' in quotes, immune to IDE refactoring, invisible to static analysis, resolved at runtime with a prayer. PHP 8.1's three-dot syntax finally made functions real values:

$upper = strtoupper(...);          // a Closure wrapping strtoupper
$upper('hello');                    // "HELLO"

$names = array_map(strtoupper(...), $names);
$valid = array_filter($emails, filter_var(...));   // partial trickery aside, it composes

The (...) isn't calling the function - it's capturing it. Works on everything callable:

$fn = trim(...);                       // built-in function
$fn = $formatter->format(...);         // instance method (binds $formatter)
$fn = Invoice::fromArray(...);         // static method
$fn = $object(...);                    // __invoke object

Why it beats the string (and array) spellings

// The old ways - stringly typed
array_map('strtoupper', $names);
array_map([$formatter, 'format'], $rows);
usort($items, [self::class, 'compare']);

Rename format() with your IDE and the array-callable version silently keeps the old name - a runtime error waiting in a code path you didn't test. The new syntax is parsed as a real reference: refactors follow it, "find usages" sees it, PHPStan type-checks its signature, and it respects visibility from the capturing scope (you can capture a private method inside the class and hand it out - the closure remembers where it was born).

Where it changes daily code

// Pipelines stop being noise
$slugs = array_map(Str::slug(...), $titles);

// Sorting by an existing comparator
usort($versions, version_compare(...));

// Event/route tables that survive refactoring
$handlers = [
    'order.paid'     => $mailer->sendReceipt(...),
    'order.refunded' => $mailer->sendRefundNote(...),
];

// Cleaner than fn() wrappers when the signature already matches
$lengths = array_map(strlen(...), $words);       // vs fn($w) => strlen($w)

The last point is the taste question people ask: when the target function's signature matches exactly, capture it; when you need to reorder, fill or adapt arguments, a short arrow closure is still right. Both beat strings every time.

Footnotes

  • Capture happens at that line: $obj->method(...) binds that $obj forever, like Closure::fromCallable did (this syntax is literally sugar for it).
  • No partial application (yet) - strtoupper(...) takes no pre-bound arguments; the RFC for that is a separate saga.
  • Sorting collections this way pairs beautifully with the spaceship operator - the sorting toolkit covers that combo.

Small syntax, but it upgrades PHP's whole functional layer from string-matching folklore to actual references. Grep your codebase for array_map(' and start the cleanup.

C
Written by Carlo Fontanos

Full-stack web developer sharing practical tutorials and building tools that ship.

Got something on your mind?

My inbox is open - no forms disappearing into the void here.

  • Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
  • Hire me for a project Have something custom in mind? Let's talk scope and timelines.
  • Product support Bought something here? I'll help you get it running.

I usually reply within 1-2 business days.

Message sent!

Your details are only used to reply to you.

Keep reading