5

With reflection, it's easy to get the start and end line e.g. of a method in the source file: ReflectionFunctionAbstract::getFileName(), ReflectionFunctionAbstract::getStartLine(), ReflectionFunctionAbstract::getEndLine() provide this functionality. However, this doesn't seem to work with properties. What's the best way to extract at least the start line and the file name of a property declaration in a class definition?

Michael
  • 197
  • 4

2 Answers2

3

It's not trivial but also not too hard.

You can get the class a property is defined in via Reflection. And from there you can get the filename. All you have to do then is either tokenize the file and check at what line the property declaration or simply go over the file line by line and do string matching.

Here is one possible way to do that:

$reflector      = new ReflectionProperty('Foo', 'bar');
$declaringClass = $reflector->getDeclaringClass();
$classFile      = new SplFileObject($declaringClass->getFileName());

foreach ($classFile as $line => $content) {
    if (preg_match(
        '/
            (private|protected|public|var) # match visibility or var
            \s                             # followed 1 whitespace
            \$bar                          # followed by the var name $bar
        /x',
        $content)
    ) {
        echo $line + 1;
    }
}

And here is a demo to show that it works

Obviously, the above solution assumes the property to be declared in a certain fashion. It also assumes you have one class per file. If you cannot be sure this is the case, tokenization is the better option. But it's also more difficult.

Gordon
  • 312,688
  • 75
  • 539
  • 559
  • Tokenization is a good idea; however, for me it would mean to go round in circles: I'm writing a [ply-like](http://www.dabeaz.com/ply/) parser generator and need this functionality to implement the tokenizer functionality. That's a kind of bootstrapping problem, so I'd probably go with your solution first and once the parser generator works, I'll implement a tokenizer solution. :) – Michael Aug 15 '13 at 17:11
  • In that case, I'd say check here first: http://stackoverflow.com/questions/2093228/lex-and-yacc-in-php. You might also be interested in https://github.com/nikic/PHP-Parser and https://github.com/scrutinizer-ci/php-analyzer – Gordon Aug 15 '13 at 17:14
  • Before starting my own project, I evaluated several existing approaches and found none to be really suitable. However, I was quite impressed by the way ply worked, so I try to adopt it to php. – Michael Aug 15 '13 at 17:20
1

Use roave/better-reflection

$classInfo = (new BetterReflection())
    ->reflector()
    ->reflectClass($class);
foreach ( $classInfo->getProperties() as $reflectionProperty) {
    $declaringClass = $reflectionProperty->getDeclaringClass()->getFileName();
    $declaringSource = $reflectionProperty->getDeclaringClass()->getLocatedSource()->getSource();
    $sourceLines = explode("\n", $declaringSource);
    $propertySource = join("\n", array_slice($sourceLines, $reflectionProperty->getStartLine(), $reflectionProperty->getEndLine()-$reflectionProperty->getStartLine()));
    $properties[$reflectionProperty->getName()] = [
        'declaringClass' => $declaringClass,
        'source' => $propertySource,
        'startLine' => $reflectionProperty->getStartLine(),
        'endLine' => $reflectionProperty->getEndLine()
    ];
}
print_r($properties);

The snippet above will also get the property declaration when that property is declared in a trait or parent class. Obviously, this can be optimized, as it's splitting the source inside the loop.

Tac Tacelosky
  • 3,165
  • 3
  • 27
  • 28