In PHP, whe have Closure::fromCallable()
. Sadly, it does not work
with FQCN strings.
This package ships a function that extends fromCallable allowing FQCN strings of invokable classes to become closures.
Just install with the following command.
composer require henzeb/closure
use function Henzeb\Closure\closure;
class InvokableClass
{
public function __invoke() {
print 'Hello World!';
}
}
class ReturnsClosures
{
public function __invoke(): Closure {
print fn()=>'Hello World!';
}
}
class NotInvokableClass
{
public function invoke() {
print 'Hello World!';
}
}
function helloWorld()
{
print 'Hello World!';
}
closure(Invokable::class)(); // prints Hello World!
closure(new InvokableClass)(); // prints Hello World!
closure(ReturnsClosure::class)(); // prints Hello World!
closure(new ReturnsClosure())(); // prints Hello World!
closure('helloWorld')(); // prints Hello World!
closure(
function(){
print 'Hello World!'
}
); // prints Hello World!;
closure(NotInvokable::class); // throws TypeError
Sometimes you may need to tell closure
how to resolve your callable
use function Henzeb\Closure\closure;
class InvokableClass
{
public function __construct(private $message)
{
}
public function __invoke() {
print $this->message;
}
}
closure(
Invokable::class,
fn(string $class) => $class('Hello World!')
); // prints Hello World!
Note: While closure
may throw a TypeError on creation, resolving of
FQCN happens on actually calling the newly created closure. Resolving
happens only the first time. Except when the invokable method returns
a Closure, then the resolving takes place first to return this closure.
Closures are adorable hacky little creatures due to their ability
to bind and change scopes. If you ever have that need
you can use bind
.
use function Henzeb\Closure\bind;
bind(
fn()=> // do what you need
, $anyThis
)('your Arg');
To use a FCQN or invokable class with binding, the __invoke
function must
return a Closure. Please note the return type! Without, it wil fail.
class InvokableClass {
public function __invoke($hello): Closure {
return $anyThis->hello($hello);
}
}
use function Henzeb\Closure\bind;
bind(InvokableClass::class, $anyThis)('your Arg');
bind(InvokableClass::class, $anyThis, AnyScope::class)('your Arg');
bind(new InvokableClass, $anyThis)('your Arg');
Bind will under the hood use closure
to get a closure, and then
binds the new this
and scope to it. It then returns a Closure you can use.
You can also directly call a (binded) closure using call
.
use function Henzeb\Closure\call;
call(
fn()=>'Hello world!'
); // returns Hello World!
call(
InvokableClass::class
); // returns whatever your callable returns
call(InvokableClass::class, $anyThis); // returns whatever your callable returns
call(
InvokableClass::class,
$anyThis,
AnyScope::class
); // returns whatever your callable returns
call(new InvokableClass, $anyThis); // returns whatever your callable returns
call(
InvokableClass::class,
$anyThis,
resolve: fn($class) => new $class('Hello World!')
); // returns whatever your callable returns
call(
fn()=>'Hello world!'
, $anyThis
); // returns Hello World!
In some cases you'd like to wrap a non-callable FQCN or class.
Each function accepts a parameter named invoke
.
class NonInvokable {
public function hello()
{
print 'Hello World!';
}
}
use function Henzeb\Closure\closure;
use function Henzeb\Closure\bind;
use function Henzeb\Closure\call;
closure(NonInvokable::class, invoke: 'hello')(); // prints Hello World!;
bind(NonInvokable::class, $newthis, invoke: 'hello')(); // prints Hello World!;
call(NonInvokable::class, $newthis, invoke: 'hello'); // prints Hello World!;
To test an object is invokable, and thus can become a closure. The function accepts any value.
use function Henzeb\Closure\invokable;
invokable(NonInvokable::class); // returns false
invokable(NonInvokable::class, 'hello'); // returns true
invokable(InvokableClass::class); // returns true
invokable([]); // returns false
invokable(STDIN); // returns false
Sometimes you may expect a boolean or a callable. using closure
,
This would fail. Using wrap
, anything that's not invokable, will
be wrapped inside a closure.
use function Henzeb\Closure\wrap;
wrap(NonInvokable::class)(); // returns NonInvokable instance
wrap(true)(); // returns true
wrap(false)(); // returns true
wrap(InvokableClass::class)(); // returns what __invoke would return
wrap(
InvokableClass::class,
invoke: 'invokableMethod'
)(); // returns what invokableMethod would return
wrap([])(); // returns an empty array
wrap(STDIN)(); // returns the STDIN stream
In some cases you might want to know the current binding of a closure or invokable.
use function Henzeb\Closure\binding;
binding(function(){})->getScope(); // returns $newScope value
binding(function(){})->getThis(); // returns $newThis value
When you have closures that uses the use
clause or when you
use static variables inside your closure you can access them
with the following:
use function Henzeb\Closure\binding;
$myVariable = 'Hello World!';
$closure = function() use ($myVariable) {
static $myStaticVariable;
$myStaticVariable = 'Hello World!'
}
binding($closure)->get('myVariable'); // returns Hello World!
binding($closure)->get('myOtherVariable'); // returns null.
binding($closure)->get('myStaticVariable'); // returns null.
$closure(); //calling the closure
binding($closure)->get('myStaticVariable'); // returns Hello World!
You can use var_dump
to print a list of all variables.
use function Henzeb\Closure\binding;
var_dump(binding(fn()=>true));
This will return an array with the current scope
,
the current this
and any static variables associated with it.
composer test
Please see CHANGELOG for more information what has changed recently.
Please see CONTRIBUTING for details.
If you discover any security related issues, please email henzeberkheij@gmail.com instead of using the issue tracker.
The GNU AGPLv. Please see License File for more information.