almservices/attributed-graphql-model-types

Attribute based graphql model/types

1.1.1 2022-08-03 19:18 UTC

This package is not auto-updated.

Last update: 2024-04-25 03:53:18 UTC


README

Latest stable version CI Status Coverage Status PHP version License

Requirements

  • Composer
  • PHP >=7.4

Installation

composer require almservices/attributed-graphql-model-types

Usage

Given we have such model

#[Model(name: "AnimalAlias")]
class Animal
{
    #[ID]
    #[Field]
    public int $id;

    #[Field]
    public string $name;
}

or with php7.4

/**
 * @Model(name: "AnimalAlias")
 */
class Animal
{
    /**
     * @ID
     * @Field
     */
    public int $id;

    /**
     * @Field
     */
    public string $name;
}

We can create GraphQL type by

class AnimalType extends ModelType
{
    public function __construct(bool $isProd)
    {
        parent::__construct(Animal::class, new TypeContainer($isProd), $isProd);
    }
}

or directly by

new ModelType(Animal::class, new TypeContainer($isProd), $isProd);

which will be equivalent to

class AnimalType extends \GraphQL\Type\Definition\ObjectType {
    public function __construct() {
        parent::__construct([
            'name' => 'AnimalAlias',
            'fields' => static fn () => [
                'id' => [
                    'resolve' => static fn (Animal $animal) => $animal->id,
                    'type' => self::nonNull(self::id()),
                ],
                'name' => [
                    'resolve' => static fn (Animal $animal) => $animal->name,
                    'type' => self::nonNull(self::string()),
                ],
            ],
        ]);
    }
}

Model Examples

Field

#[Model(name: "Foo")]
class Foo
{
    #[Field]
    public int $bar;

    #[Field]
    public function baz(): string
    {
        return 'baz';
    }
}

Alias

#[Model(name: "AnimalAlias")]
class Foo
{
    #[Alias("bar")]
    #[Field]
    public int $foo;
}

Enum

PHP 8.1

#[Model(name: "Family")]
enum Family
{
    case SEAL;
    case BEAR;
}

inline version for PHP < 8.1

#[Model(name: "Foo")]
class InlineEnum
{
    #[Field]
    #[Enum("SingleEnum", "A", "B", "C", "D")]
    public string $single;

    #[Field]
    #[ListOf("ListEnum")]
    #[Enum("ListEnum", "A", "B", "C", "D")]
    public array $list;
}

other options are:

#[Model("SomeEnum")]
class SomeEnum: string
{
    case FOO = 'foo'; // FOO
}
#[Model("SomeEnum")]
class SomeEnum: int
{
    case FOO = 1; // FOO
}
#[Model("SomeEnum")]
class SomeEnum: string
{
    #[Alias("foo")]
    case FOO = "bar"; // foo
}
#[Model("SomeEnum")]
class SomeEnum: int
{
    #[Alias("foo")]
    case FOO = 1; // foo
}

Ignoring specific fields:

#[Model("SomeEnum")]
class SomeEnum: int
{
    case FOO;
    case BAR;
    #[Ignore]
    case BAZ;
}

Lists

#[Model("Foo")]
class Foo
{
    #[Field]
    #[ListOf(type: "string")]
    public \Traversable $test; // [String]!

    #[Field]
    #[ListOf(type: "string")]
    public array $foo; // [String]!

    #[Field]
    #[ListOf(type: self::class)]
    public iterable $bar; // [Foo]!

    #[Field]
    #[ListOf(type: OtherModel::class)]
    public array $baz; // [OtherModel]!

    #[Field]
    #[ListOf(type: OtherModel::class)]
    public \Doctrine\Common\Collections\Collection $qux; // [OtherModel]!
}

for non-nullable items use NonNull

#[Model("Foo")]
class Foo {
    #[Field]
    #[NonNull]
    #[ListOf("string")]
    public array $list; // [String!]!
}

Value Object

Legacy or custom Value Objects that can be cast to string, can be used as model property

class FooBar implements Stringable
{
    private string $type;

    private function __construct(string $type)
    {
        $this->type = $type;
    }

    public static function foo(): self
    {
        return new self('foo');
    }

    public static function bar(): self
    {
        return new self('foo');
    }

    public function __toString(): string
    {
        return $this->type;
    }
}

example:

#[Model(name: "Foo")]
class Foo {
    #[ID]
    #[Field]
    public readonly FooBar $id;
}

but if value is more complex it can become resolved on demand

#[Model(name: "Foo")]
class Foo {
    public function __construct(
        private string $foo,
        private string $bar,
    ) {}

    #[ID]
    #[Field]
    public function id(): string
    {
        return $this->foo . $this->bar;
    }
}

Deprecated

#[Model(name: "Foo")]
class Foo {
    #[Field]
    #[Deprecated("Do not use Foo.foo, use Foo.bar instead")]
    public function foo(): string
    {
        return 'foo';
    }

    #[Field]
    public function bar(): string
    {
        return 'bar';
    }
}

Description

#[Description("Foo is Bar")]
#[Model(name: "Foo")]
class Foo {
    #[Field]
    #[Description("Foo foo?")]
    public function foo(): string
    {
        return 'foo';
    }
}

More on resolvers

#[Model("Foo")]
class Foo {
    #[Field]
    #[Argument(name: "bar", type: "string", nullable: false)]
    #[Argument(name: "baz", type: "[String!]!")]
    #[Argument(name: "qux", type: "[string]", nullable: false)]
    public function bar(
        #[ArrayShape([
            "baz" => "string[]"
        ])]
        array $args
    ): string
    {
        return implode(", ", $args['baz']);
    }
}

Example with objective input type

class MyInput extends InputObjectType {
    public function __construct()
    {
        parent::__construct("MyInput", $this->fields());
    }

    /**
     * @return \Generator<FieldInterface>
     */
    private function fields(): \Generator
    {
        yield new Field("foo", Type::nonNull(Type::string()));
    }
}

// we need to register MyInput into map
$typeContainer = new TypeContainer();
$typeContainer->set(MyInput::class, new MyInput());
$typeContainer->set("MyInput", new MyInput());

#[Model("Foo")]
class Foo {
    #[Field]
    #[Argument(name: "baz", type: MyInput::class, nullable: false)]
    public function bar(
        #[ArrayShape([
            "baz" => [
                "foo" => "string"
            ]
        ])]
        array $args
    ): string
    {
        return $args['baz']['foo'];
    }
}

Example of fully qualified resolver

#[Model("Foo")]
class Foo {
    #[Field]
    public function bar(
        #[ArrayShape([])] array $args,
        Context $context,
        ResolveInfo $resolveInfo
    ): string
    {
        return '';
    }
}

Demo

To run demo execute

php -S 127.0.0.1:8000 demo/index.php

curl 127.0.0.1:8000 -d '{"query": "{myUser{id firstName lastName}}"}' -H "Content-Type: application/json" -H 'Authorization: dev'