Get PhpDoc @params of a method

A well-written PhpDoc is a goldmine of information. It may feel redundant, however, to document values that you use in your code too. Why not parse the PhpDoc itself and use that value? Reflection is the way to go.

Consider an Api class that requests users by ID from the Api’s user endpoint. We like to document the name of this endpoint in the PhpDoc for documentation purposes.

class Api {
/**
*
@param int $id
*
@return User
*
@endpoint users
*/
public function user(int $id) : User {
$this->request('users', $id);
}
}

Let see if we can improve this. Let’s replace the ‘users’ string by a method ->endpoint().

public function user(int $id) : User {
$this->request($this->endpoint(), $id);
}

The method is to provide us with the value of the @endpoint argument.

protected function endpoint() : string {
// derive the endpoint from calling method
}

To differentiate between calling methods, we will simply inject the method’s name through a magic constant into the endpoint method. Then we will use class Reflection to provide us with information about this calling class.

public function user(int $id) : User {
$this->request($this->endpoint(__METHOD__), $id);
}

protected function endpoint(string $method) : string {
$reflection = (new ReflectionClass(self::class))->getMethod($method);
// derive the endpoint from $reflection
}

We will write a helper function to retrieve the arguments from the method. The helper method will just return an associative array of parameter names and corresponding values.

protected function endpoint(string $method) : string {
$reflection = (new ReflectionClass(self::class))->getMethod($method);
$params = phpdoc_params($reflection);
return $params['@endpoint'][0] ?? null;
}

For brevity, the try block around ReflectionClass is omitted from this example.

Now, how would our phpdoc_params method look like? Well, we should retrieve the DocBlock, trim and extract all lines, filter lines with @params and then parse each line:

function phpdoc_params(ReflectionMethod $method) : array
{
// Retrieve the full PhpDoc comment block
$doc = $method->getDocComment();

// Trim each line from space and star chars
$lines = array_map(function($line){
return trim($line, " *");
}, explode("\n", $doc));

// Retain lines that start with an @
$lines = array_filter($lines, function($line){
return strpos($line, "@") === 0;
});

$args = [];

// Push each value in the corresponding @param array
foreach($lines as $line){
list($param, $value) = explode(' ', $line, 2);
$args[$param][] = $value;
}

return $args;
}

And that’s it! There is so much fun in PHP’s reflection mechanisms. Check it out on 3v4l.

Take note that this approach does impact performance so use it at the right place and/or cache the values at run-time. In the end, it’s all about balancing the qualities of your code. Thanks for reading.