Skip to content

Instantly share code, notes, and snippets.

@binotaliu
Last active December 11, 2021 10:16
Show Gist options
  • Save binotaliu/5479edace5f1079f99751b633d460937 to your computer and use it in GitHub Desktop.
Save binotaliu/5479edace5f1079f99751b633d460937 to your computer and use it in GitHub Desktop.
FnChain

FnChain

FnChain is an experimental helper I came up when dealing with following nesting closure:

return $query
    ->where('is_enabled', true)
    ->where('available_from', '<=', DB::raw('NOW()'))
    ->where(
        fn (Builder $q) => $q
            ->whereNull('available_until')
            ->orWhere(
                fn (Builder $q) => $q
                    ->whereNotNull('available_until')
                    ->where('available_until', '>', DB::raw('NOW()'))
            )
    );

I want to avoid fn (Builder $q) => $q. With FnChain, above code can be rewrite to:

return $query
    ->where('is_enabled', true)
    ->where('available_from', '<=', DB::raw('NOW()'))
    ->where(
        FnChain
            ::whereNull('available_until')
            ->orWhere(
                FnChain
                    ::whereNotNull('available_until')
                    ->where('available_until', '>', DB::raw('NOW()'))
                    ->toClosure()
            )
            ->toClosure()
    );

But the toClosure is anoying me, maybe we can use Closure::fromCallable to get ride of it?

return $query
    ->where('is_enabled', true)
    ->where('available_from', '<=', DB::raw('NOW()'))
    ->where(Closure::fromCallable(
        FnChain
            ::whereNull('available_until')
            ->orWhere(Closure::fromCallable(
                FnChain
                    ::whereNotNull('available_until')
                    ->where('available_until', '>', DB::raw('NOW()'))
            ))
    ));
<?php
declare(strict_types=1);
namespace App\Utils;
use Closure;
final class FnChain
{
private $calls = [];
private function __construct()
{
}
public static function start(): self
{
return new FnChain();
}
public function toClosure(): Closure
{
return function ($start) {
$current = $start;
foreach ($this->calls as $call) {
$current = $current->{$call['fn']}(...$call['args']);
}
return $current;
};
}
public function __invoke(mixed $start): mixed
{
return ($this->toClosure())($start);
}
public function __call(string $name, array $arguments): self
{
$this->calls[] = [
'fn' => $name,
'args' => $arguments,
];
return $this;
}
public static function __callStatic(string $name, array $arguments): self
{
return self::start()->{$name}(...$arguments);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment