Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use PHPDoc to type-hint the parameters of a Callable?

I have a method that accepts a callback as a parameter. I would like to provide a signature in the PHPDoc for the class method that outlines the parameters for the callback function to be passed to that method so that my IDE (PHPStorm) can produce valid type hints for functions that are passed to my method, or at least someone looking at the code can determine the signature of the callback they're intended to provide.

For example:

class Foo {
  public $items = [];
  /**
  * @param Callable(
  *   @param ArrayObject $items The list of items that bar() will return
  * ) $baz A callback to receive the items
  **/
  public function bar(Callable $baz) {
    $items = new ArrayObject($this->items);
    $baz($items);
  }
}

The method bar has one parameter, $baz, which is a callback function. Any function passed as an parameter to bar() must accept an ArrayObject as its only parameter.

Ideally, it should be possible to include multiple parameters for the Callable, just like for any other method.

When I write the following code:

$foo = new Foo();
$foo->bar(function(

...I should then receive a parameter list that correctly hints the type (ArrayObject) of the accepted parameter for this function call.

Is such a thing possible? Does PHPStorm or another IDE support it? Is there a recommended/standard way of documenting this even if there is no IDE support?

like image 816
ringmaster Avatar asked Nov 30 '12 16:11

ringmaster


3 Answers

PHP 7+:

Using an interface for the callable combined with anonymous classes will do the trick. It's not very handy and leads to bit too complex code for the class-consumer, but currently it's the best solution in terms of static code-analysis.

/**
 * Interface MyCallableInterface
 */
interface MyCallableInterface{
    /**
     * @param Bar $bar
     *
     * @return Bar
     */
    public function __invoke(Bar $bar): Bar;
}

/**
 * Class Bar
 */
class Bar{
    /**
     * @var mixed
     */
    public $data = null;
}

/**
 * Class Foo
 */
class Foo{
    /**
     * @var Bar
     */
    private $bar = null;

    /**
     * @param MyCallableInterface $fn
     *
     * @return Foo
     */
    public function fooBar(MyCallableInterface $fn): Foo{
        $this->bar = $fn(new Bar);
        return $this;
    }
}

/**
 * Usage
 */
(new Foo)->fooBar(new class implements MyCallableInterface{
    public function __invoke(Bar $bar): Bar{
        $bar->data = [1, 2, 3];
        return $bar;
    }
});

If you're using PhpStorm it will even auto-generate the __invoke-Method's signature & body within the anonymous class.

like image 196
rckd Avatar answered Nov 20 '22 07:11

rckd


I overcame this problem by defining a static function within the class using the callable. That function has its own doc-block and I just refer to it in the method that's requiring my callable using PHPDoc's @see tag.

class Foo
{
    /**
     * Description of the "bar" callable. Used by {@see baz()}.
     *
     * @param int $index A 1-based integer.
     * @param string $name A non-empty string.
     * @return bool
     * @see baz()
     * @throws \Exception This is a prototype; not meant to be called directly.
     */
    public static barCallable($index, $name)
    {
        throw new \Exception("barCallable prototype called");
    }

    /**
     * Description of the baz() method, using a {@see barCallable()}.
     *
     * @param callable $bar A non-null {@see barCallable()}.
     * @see barCallable()
     */
    public function baz(callable $bar)
    {
        // ...
        call_user_func($bar, 1, true);
        // ...
    }
}

This works well in PhpStorm 10. the Quick Documentation allows to navigate from the method documentation to the prototype documentation easily.

I make my prototype function throw an exception to make it clear it's not meant to be called. I could use a protected or private scope, but then PHPDoc would not always pick the doc-block for documentation generation.

Unfortunately PhpStorm can't track usage of callbacks. It doesn't provide Parameter Info either when using the method requiring a callback, but the callback is at least formally documented.

This approach also has the added benefit of validating a callback definition from the reflection of the prototype at run-time.

like image 43
Percutio Avatar answered Nov 20 '22 07:11

Percutio


It is not possible in PhpStorm by now. I can't even think of other solution which do relatively the same by other means.

like image 3
ualinker Avatar answered Nov 20 '22 08:11

ualinker