How to PHPDoc an anonymous function when passed as an argument?
Asked Answered
F

3

9

How should I document an anonymous function when it's passed as an argument? For example:

// Call my_function(), passing 2 arguments.
my_function( 'foo', function() {
    // Body of the anon function I'd like to document.
} );

Thanks in advance.

Flywheel answered 30/12, 2015 at 19:12 Comment(1)
@MarkBaker that'd be the type but I'm wondering if a DocBlock is necessary, and if so where to put it etc. Is there a standard way to document this type of situation?Flywheel
F
4

To document that a function accepts a Closure, I'd suggest callable:

/**
 * Do something.
 * @param callable $code
 */
function foo(callable $code) {
}

Regarding the commentary, PHPDoc uses DocBlocks, which the PHP engine Tokenizer only recognizes above formal definitions. Thus, PHPDoc will not see this:

/**
 * My closure.  PHPDoc will *not* parse this, because it's not a formal definition.
 * @param string $name
 */
$closure = function ($name) { return $name; };
Frayda answered 30/12, 2015 at 19:15 Comment(7)
Thanks, but isn't that documenting the param in the function's definition? My situation is slightly different. I need to document the argument in the function call when the argument happens to be a function itself.Flywheel
Yes, it is. Are you wanting to document the type in the caller? If so, I don't recall ever having seeing that before... Maybe I'm not getting what you want to document. :)Frayda
I've not seen it before either, I suppose that's why I'm asking. I feel anonymous functions are equal to named functions aside from their unbound nature. So my guess is they also need to be documented, but I have no idea how.Flywheel
Usually closures are self-evident and don't need much documentation beyond say a passing end-of-line comment. If you find your closure needs to explain why it works the way it does, consider making it a proper function. You'll have to do that anyway, because PHPDoc only supports DocBlocks, which only exist as /** */ just above a definition.Frayda
I was thinking the same, although making a function non-anonymous purely because there's no easy way to document it doesn't feel right. You're right that the anon function code example I gave in my question is very simple and doesn't need explaining, but imagine if it took parameters, there would be a greater need to document it.Flywheel
In my experience, if a closure has needed documentation, then it's tended to be vital and therefore deserved formal naming. Maybe that's just my style. At any rate, see my edit, which addresses this. Bottom line: PHPDoc will not see your comment unless it's in a DocBlock, and DocBlocks are only seen on formal definitions. See also https://mcmap.net/q/761325/-are-there-any-php-docblock-parser-tools-available-closed/2908724 and https://mcmap.net/q/551227/-syntax-of-closure-in-phpdoc/2908724.Frayda
Thanks for the edit. I got into this situation because I didn't want to pollute the global space with a function name that could result in collisions (I know I can use namespaces for that but this became an interesting question so I thought I'd ask). Indeed, I guess house style will need to be the solution here because there seems to be no formal way to document this situation at this point.Flywheel
H
3

This has worked for me so far:

interface CustomCallback
{
    /**
     * @return string
     */
    public function __invoke();
}

/**
 * @param string $a
 * @param CustomCallback $b
 * @return void
 */
my_function($a, $b) {

}

This tells us that the second parameter of my_function expects something that implements the CustomCallback interface. By looking in the CustomCallback interface, we can see that it's callable because of the __invoke method. Then by looking at the __invoke method's documentation, we see what parameters it expects and the return type.

Hambley answered 21/5, 2019 at 15:50 Comment(0)
T
0

Since this thread is well indexed in search engines and there's no valid answer here, I would like to leave a note how to correctly annotate an anonymous function in terms of its parameters, as well as the output.

This way of documenting anonymous functions in PHP is understood by modern IDEs (like PHPStorm, as well as static analysis tools like PHPStan or Psalm.)


Imagine a function that accepts a callable. The callable is expected to return an array of users based on some filter criteria provided to it by the caller. In this case let's pass an array of user IDs expected as the first argument and some additional state criteria in the second.

Here's a valid example of this function including the doc block:

/**
 * @param callable(array<int>,array<string,mixed>):array<User> $userProvider
 *
 * @return array<array{id:int, name:string}>
 */
function usersToArray(callable $userProvider): array
{
    $result = [];

    foreach ($userProvider([1, 2], ['active' => true]) as $user) {
        // PHPStorm knows that $user is a User entity

        $result[] = [
            'id'   => $user->getId(),
            'name' => $user->getName(),
        ];
    }

    return $result;
}

Let's break down the callable's annotation:

callable(array<int>,array<string,mixed>):array<User>

It describes:

  • a callable callable(…)
  • that requires two arguments:
    • an array of integers array<int>
    • an array of some arguments indexed by a string column array<string,mixed>
  • it returns an array of User entities array<User>
Tia answered 7/9 at 9:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.