php-standard-library/fun
Functional programming utilities for PHP: compose and pipe callables, decorate functions, and control execution (memoize, throttle, debounce, retry, etc.). Part of PHP Standard Library with focused, reusable helpers for cleaner functional-style code.
Installation Add the package to your Laravel project:
composer require php-standard-library/fun
No service provider or configuration is needed—it’s a standalone utility library.
First Use Case: Middleware Decoration
Replace traditional middleware grouping with functional composition in app/Http/Kernel.php:
use Fun\Decorators\decorate;
use Fun\Compose\pipe;
protected $middlewareGroups = [
'web' => [
decorate(
\App\Http\Middleware\EncryptCookies::class,
fn($next) => fn($request) => logger()->info('Encrypting cookies')
),
// More middleware...
],
];
First Use Case: Request Transformation Compose request transformations in a controller:
use Fun\Compose\pipe;
use Illuminate\Http\Request;
public function store(Request $request)
{
$data = pipe(
$request->all(),
fn($data) => array_filter($data), // Filter empty values
fn($data) => collect($data)->toArray(), // Ensure array
fn($data) => $this->validateData($data) // Custom validation
);
// Process $data...
}
Where to Look First
Fun/Compose (for pipelines) and Fun/Decorators (for wrapping).pipe() helper or middleware stack.Pattern: Replace grouped middleware with functional decorators. Example:
// app/Http/Kernel.php
protected $middleware = [
decorate(
\App\Http\Middleware\TrimStrings::class,
fn($next) => fn($request) => logger()->debug('Trimming strings', ['request' => $request->all()])
),
decorate(
\App\Http\Middleware\ValidateCsrfToken::class,
fn($next) => fn($request) => $this->checkForJsonRequest($request)
),
];
Workflow:
decorate().Pattern: Decorate repositories/services with cross-cutting concerns. Example:
// app/Services/UserService.php
class UserService {
public function __construct(private UserRepository $repository) {}
public function getWithDecorators(User $user) {
return pipe(
$this->repository->find($user->id),
tap(fn($user) => logger()->info("Fetched user {$user->email}")),
memoize(ttl(60)), // Cache for 60 seconds
);
}
}
Workflow:
pipe() to chain transformations, logging, or caching.memoize() or retry() for performance/resilience.Pattern: Compose pre/post-processing in Laravel jobs or commands. Example:
// app/Jobs/ProcessOrder.php
public function handle() {
$order = pipe(
$this->fetchOrder(),
tap(fn($order) => $this->logOrderCreation($order)),
fn($order) => $this->validateOrder($order),
fn($order) => $this->notifyCustomer($order),
);
}
Workflow:
pipe() to chain them sequentially.tap() for side effects (logging, notifications).Pattern: Chain event listeners functionally. Example:
// app/Listeners/ProcessUserRegistered.php
public function handle(UserRegistered $event) {
pipe(
$event->user,
tap(fn($user) => $this->sendWelcomeEmail($user)),
tap(fn($user) => $this->logUserRegistration($user)),
fn($user) => $this->createUserProfile($user),
);
}
Workflow:
EventServiceProvider.pipe() and tap() to compose side effects.Pattern: Transform requests/responses in route middleware. Example:
// app/Http/Middleware/TransformResponse.php
public function handle($request, Closure $next) {
return pipe(
$next($request),
fn($response) => $response->setContent(json_encode($response->getContent(), JSON_PRETTY_PRINT)),
fn($response) => $response->header('X-Transformed', 'true'),
);
}
Workflow:
pipe() to modify the response object.Pattern: Create reusable test pipelines. Example:
// tests/TestHelpers.php
function assertPipeline($input, array $transformations, $expected) {
$result = pipe($input, ...$transformations);
$this->assertEquals($expected, $result);
}
// Usage in test
assertPipeline(
['name' => 'John', 'age' => 30],
[
fn($data) => array_filter($data),
fn($data) => collect($data)->toArray(),
],
['name' => 'John']
);
Leverage Laravel’s Container Bind composed functions to the container for reuse:
$this->app->bind('transform.request', fn() => pipe(
fn($request) => $request->all(),
fn($data) => array_filter($data),
));
Combine with Laravel’s pipe()
Use Fun\Compose\pipe alongside Laravel’s pipe() for consistency:
use Fun\Compose\pipe as funPipe;
use Illuminate\Support\Facades\Pipe;
$result = funPipe($input, fn($x) => $x + 1);
$laravelResult = Pipe::through($input, [fn($x) => $x * 2]);
Type Safety with PHP 8.1+ Use typed closures for better IDE support:
$add = fn(int $a, int $b): int => $a + $b;
$result = $add(1, 2); // Type-checked
Error Handling
Wrap composed functions in try-catch or use Fun\Control\try:
use Fun\Control\try;
$result = try(
fn() => pipe($data, fn($x) => $x / 0), // Will throw
fn($e) => logger()->error($e->getMessage()),
fn() => null
);
Debugging Complexity
Fun\Compose\tap to log intermediate values:
$result = pipe(
$data,
tap(fn($x) => logger()->debug('Step 1', ['data' => $x])),
fn($x) => $x * 2,
);
Performance Overhead
// Bad: Too many decorators
$slow = pipe($data, ...array_fill(0, 10, fn($x) => $x));
// Good: Minimal composition
$fast = pipe($data, fn($x) => $x + 1, fn($x) => $x * 2);
Closure Scope Issues
use to bind variables explicitly:
$userId = 1;
$getUser = fn() => User::find($userId); // Late binding: $userId may change!
// Fixed
$getUser = fn() => User::find($userId); // Works if $userId is captured
// OR
$getUser = fn() use ($userId) => User::find($userId); // Explicit capture
Laravel-Specific Quirks
How can I help you explore Laravel packages today?