phpDocumentor/TypeResolver

ContextFactory does not collect use statements in class traits

Closed this issue · 2 comments

tezvi commented

Hi everyone!

public function createForNamespace($namespace, $fileContents)
ContextFactory does not follow traits to collect used / imported namespaces.

Example:

<?php
namespace Acme\Example;

use Acme\Other\Example\Bar;

trait FooTrait {
 /**
   * @var Bar
   */
  public $bar;
}
<?php
namespace Acme\Example;

use Acme\Example\FooTrait;

class Foo {
  use FooTrait;
}

When collecting namespaces the Foo::$bar property will be described as \Acme\Example\Bar instead of \Acme\Other\Example\Bar.
The solution would be to also process T_USE tokens after the parser hits T_CLASS token. Additionally trait files should be loaded and processed against nested traits :)

case T_CLASS:
// Fast-forward the iterator through the class so that any
// T_USE tokens found within are skipped - these are not
// valid namespace use statements so should be ignored.
$braceLevel = 0;
$firstBraceFound = false;
while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) {
if ($tokens->current() === '{'
|| $tokens->current()[0] === T_CURLY_OPEN
|| $tokens->current()[0] === T_DOLLAR_OPEN_CURLY_BRACES) {
if (!$firstBraceFound) {
$firstBraceFound = true;
}
++$braceLevel;
}
if ($tokens->current() === '}') {
--$braceLevel;
}
$tokens->next();
}
break;
case T_USE:
if ($currentNamespace === $namespace) {
$useStatements = array_merge($useStatements, $this->parseUseStatement($tokens));
}
break;

This library detects types per file so it would be impossible to discover the types defined in a trait. We are doing this for performance and collision reasons. So even if we are parsing the body for the class we would not be able to load the right trait since we don't know where it is located.

Beside that the use case you are describing is a way more complicated use case. since the class Foo will only contain the property $bar at runtime. If you want this kind of information please have a look at https://github.com/phpdocumentor/reflection which is collecting the information of each phpfile and reflects all elements that are defined in each file. If you provide multiple files it will be able to create a full tree of your projects namespaces including all classes. traits and there properties.

In the end the class Foo will never be reflected with the poperty $bar since it is not defined in the class but only available at runtime. Which is out of scope of the library. But it can provide you the information you need if you interpret the results in the correct way.

Hopefully this helps you to find a solution for the project you are working on.

tezvi commented

Thx for detailed explanation @jaapio !
I'll definitely have a look at phpdocumentor/reflection.

There is a quick workaround though. I can force unused use statements in class Foo file:

<?php
namespace Acme\Example;

use Acme\Example\FooTrait;
use Acme\Other\Example\Bar; // <-- HERE

class Foo {
  use FooTrait;
}

And the Foo::$bar type will be annotated properly. However, unsed use statements will probably be removed by static code analyzers or code cleanup tools so not a solution for a long run :\

Cheers