thecodingmachine / graphqlite

Use PHP Attributes/Annotations to declare your GraphQL API

Home Page:https://graphqlite.thecodingmachine.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Defining fields in type by trait from different namespace ends with error for relative type declarations

josefsabl opened this issue · comments

Let's assume this class/trait structure:

MyTrait
Type1
Type2
SomeNamespace\Type3

TypeX are Graphqlite types.

MyTrait defines field that returns array of Type2 and is used both in Type1 and Type3.

Let's say it looks like this:

trait MyTrait
{
    /**
     * @return Type2[]
     */
    #[Graph\Field]
    public function type2s(): array;
}

This trait works in Type1 but throws an error in Type3.

ReflectionException: Class "\SomeNamespace\Type2" does not exist
in /var/www/app/vendor/thecodingmachine/graphqlite/src/Mappers/Root/IteratorTypeMapper.php:50

Workaround is to declare the return type in trait using absolute path.

For native typehints there is no problem.

@josefsabl This looks like a normal PHP namespacing misunderstanding. Traits are effectively copied into the class they're evaluated within at runtime. So, Type3 is in the SomeNamespace namespace. Therefore, the annotation you've provided for the @return would mean it's evaluated as SomeNamespace\Type2. This is not a GraphQLite issue and more of a misunderstanding for how namespacing works.

I am sorry, but that is not exactly true. There is no problem with namespaces or understanding them.

Of course that typehints in traits are evaluated from the context (namespace) where the trait is defined and not from the context where the trait is used.

What you say would mean that either a) No relative typehints can be used in traits or b) Traits can be used only in namespace where they are defined. And neither is true.

Like I already said, native relative typehints in same trait work as expected.

Let's see:

namespace App

trait MyTrait
{
    public function foo(): Type2 { return new Type2; }
}
namespace App;

final class Type1
{
    use MyTrait;
}
namespace App\SomeNamespace;

final class Type3
{
    use \App\MyTrait;
}
echo (new \ReflectionClass(\App\Type1::class))
    ->getMethod('foo')
    ->getReturnType();

echo (new \ReflectionClass(\App\SomeNamespace\Type3::class))
    ->getMethod('foo')
    ->getReturnType();

Both of these echos print same correct class of course: \App\Type2.

BUT !!!

I know where are you coming from. The problem lies in the reflection that is used to parse the docblock and the fact that there is no sane way to find out if the method is declared in the class or in the trait.

\ReflectionClass has getTraits method and I believe it could be used, but I understand this would be hassle and the problem has easy workaround.

Maybe the issue should be left open for others, I still believe this is a bug.

Anyway, thanks for great library!