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
composer require php-standard-library/fun
No Laravel-specific configuration is needed—it integrates seamlessly with PHP closures and Laravel’s service container.
First Use Case: Middleware Decorator Replace a traditional middleware class with a functional decorator:
use Fun\Decorators\decorate;
use Illuminate\Http\Request;
$middleware = decorate(
fn(Request $request) => $request->next($request),
fn($next) => fn(Request $request) =>
logger()->info('Request started') &&
$next($request) &&
logger()->info('Request completed')
);
Where to Look First
Fun\Compose (for pipe, tap, compose).Fun\Control (for try, retry, race).Fun\Decorators (for decorate, memoize, timeout).app() for dependency injection:
$decoratedService = decorate(
app()->make(MyService::class),
fn($next) => fn() => logger()->debug('Service called') && $next()
);
Workflow: Replace Laravel’s Kernel::middleware() groups with functional decorators.
// app/Http/Kernel.php (partial)
protected $middlewareGroups = [
'web' => [
decorate(
fn($request) => $request->next($request),
fn($next) => fn($request) =>
auth()->check() || redirect('/login'),
fn($next) => fn($request) =>
$next($request)->header('X-Processed', 'true')
),
],
];
Pattern: Decorate repositories/services with cross-cutting concerns.
// app/Services/UserService.php
$userService = decorate(
app()->make(UserRepository::class),
fn($next) => fn($userId) =>
cache()->remember("user:$userId", 60, fn() => $next($userId)),
fn($next) => fn($userId) =>
logger()->info("Fetching user $userId") && $next($userId)
);
Use Case: Chain pre/post-processing in Laravel jobs or Artisan commands.
// app/Jobs/ProcessOrder.php
public function handle() {
$result = pipe(
$this->order,
fn($order) => $order->validate(),
fn($order) => $order->charge(),
fn($order) => $order->notify(),
fn($order) => $order->archive()
);
}
Pattern: Compose event listeners into pipelines.
// EventServiceProvider
protected $listen = [
'order.placed' => [
decorate(
fn($event) => null,
fn($next) => fn($event) =>
$this->logOrder($event) && $next($event),
fn($next) => fn($event) =>
$this->sendNotification($event) && $next($event)
),
],
];
Integration Tip: Use pipe to transform requests/responses in route middleware.
// app/Http/Middleware/TransformResponse.php
public function handle($request, Closure $next) {
return pipe(
$next($request),
fn($response) => $response->header('X-API-Version', 'v1'),
fn($response) => $response->json(['data' => $response->original])
);
}
Pattern: Create reusable test decorators.
// tests/TestHelpers.php
function mockWithRetry($closure, $maxAttempts = 3) {
return retry($closure, $maxAttempts);
}
// tests/Feature/OrderTest.php
test('order processing retries on failure', function() {
mockWithRetry(fn() => $this->failOrder())->shouldBeCalled();
});
Closure Scope Issues
use ($var) explicitly or leverage Fun\Compose::curry for partial application.
// Bad: Implicit capture
$userId = 1;
$getUser = fn() => User::find($userId); // $userId might change!
// Good: Explicit or curried
$getUser = curry(fn($id) => User::find($id))($userId);
Debugging Composed Functions
Fun\Compose::tap to log intermediate steps:
$result = tap(
$composedFunction,
fn($step) => logger()->debug("Step: $step")
);
Performance Overhead
blackfire.io and limit decorators to 3–5 per pipeline.Laravel Container Conflicts
app()->make() inside decorators or bind decorated versions explicitly:
$app->bind(MyService::class, fn($app) =>
decorate($app->make(MyService::class), /* ... */)
);
Async/Await Limitations
Fun\Control does not support native PHP async/await.Spatie\Async or Amp for async workflows.$decorated = decorate(
$original,
fn($next) => fn($arg) =>
logger()->debug("Before: $arg") &&
$next($arg) &&
logger()->debug("After: $arg")
);
Fun\Compose::inspect:
$composed = inspect($addFive, $multiplyByTwo);
// Outputs: [Function#0, Function#1]
boot() method).app/Helpers/fun.php).Custom Decorators
Extend Fun\Decorators\Decorator to create domain-specific decorators:
class CacheDecorator extends Decorator {
public function __invoke($next) {
return fn($key) =>
cache()->remember($key, 60, fn() => $next($key));
}
}
Laravel Facade Wrapper Create a facade to integrate with Laravel’s conventions:
// app/Facades/Fun.php
class Fun extends Facade {
protected static function getFacadeAccessor() {
return 'fun';
}
}
Bind it in a service provider:
$app->singleton('fun', fn() => new Fun\Fun());
Event Decorators Decorate Laravel events for side effects:
Event::listen('order.created', decorate(
fn($event) => null,
fn($next) => fn($event) =>
$this->dispatch(new OrderCreatedEvent($event->order)),
fn($next) => fn($event) =>
$this->logEvent($event)
));
pipe():
use Illuminate\Pipeline\Pipeline;
$result = resolve(Pipeline::class)
->send($request)
->through([$middleware1, $middleware2])
->thenReturn();
$expensiveCall = memoize(fn() => $this->fetchDataFromExternalApi());
$timeoutCall = timeout(fn() => $this->slowOperation(), 5); // 5 seconds
How can I help you explore Laravel packages today?