Tofandel
Tofandel

Reputation: 3565

Building a function from an arbitrary callable with a matching reflection in PHP

Lets say I have an arbitrary function taking some parameters and returning say an array (the return and what the method does doesn't matter)

$callable = fn (MyClass $myClass, string $id, bool $rec) => []

I would like to build a new function from this closure so that this new function takes the exact same parameters in a way that the Reflection of this function will have the same parameters as the original

What I mean:

$clonedCallable = someMagic($callable);
assertEquals( // Equal but obviously not the same ref
  (new ReflectionFunction($callable))->getParameters(),
  (new ReflectionFunction($clonedCallable))->getParameters()
);
$callable(); // []
$clonedCallable(); // Wrapper([])

I'm basically trying to wrap a closure, in a way that would not lose reflection information

fn (...$args) => $callable(...$args); // This would not work
fn (MyClass $myClass, string $id, bool $rec) => $callable($myClass, $id, $rec); // I need to generate this

Is there a way in PHP to build this kind of method without using some hackjob eval (which is the only idea I have right now) as Reflection doesn't have some kind of magic setParameters()?

Upvotes: 0

Views: 55

Answers (1)

the_wizard
the_wizard

Reputation: 592

<?php

   class Foo { }

   $add = function (int $a, Foo $foo): array {
             return [$a * 2];
         };

   function wrapAction(string|callable|array $action, callable $wrapper = null): callable
   {
        $rc = is_array($action) ?
            new ReflectionMethod($action[0], $action[1]) :
            new ReflectionFunction($action);
        $args = [];
        $action = $rc->getClosure();
        if (!$wrapper) {
            $wrapper = fn ($res) => $res;
        }
        foreach ($rc->getParameters() as $arg) {
            $args[] = $arg->getType() . ' $' . $arg->getName();
        }
        $args = join(',', $args);
        return eval("return fn ($args) => \$wrapper(\$action(...func_get_args()));"); // I know, but I can't think of any way other than this ;)
    }


   // test
   $clone = wrapAction($add, fn ($res) => $res + 2);
   $orgRef = new ReflectionFunction($add);
   $cloneRef = new ReflectionFunction($clone); 

   assert($orgRef->getParameters() == $cloneRef->getParameters(), "Not the same");

   $foo = new Foo();
   print_r($add(5, $foo)); // 10
   print_r($clone(5, $foo)); // 12

Upvotes: 1

Related Questions