uxf/gql

Maintainers

Details

gitlab.com/uxf/gql

Source

Issues

Installs: 7 523

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Forks: 0


README

Install

$ composer req uxf/gql

Config

// config/packages/uxf.php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
    $containerConfigurator->extension('uxf_gql', [
        'destination' => __DIR__ . '/../generated/schema.graphql', // or array
        'sources' => [
            __DIR__ . '/../src',
        ],
        'injected' => [
            // class => resolver
            Coyote::class => CoyoteResolver::class,
        ],
        'register_psr_17' => false, // default true
        'allow_money' => true, // default false
        'debug_flag' => DebugFlag::INCLUDE_DEBUG_MESSAGE, // default
    ]);
};

Supports by default

  • all PHP native types (native BackedEnum too)
  • UXF\Core\Type\Date
  • UXF\Core\Type\DateTime
  • UXF\Core\Type\Time
  • Ramsey\Uuid\UuidInterface

Usage

Query

  • #[Query(name: 'name')]
use TheCodingMachine\GraphQLite\Annotations\Query;

class ArticlesQuery
{
    public function __construct(private readonly ArticleProvider $articleProvider) {}

    /**
     * @return Article[]
     */
    #[Query(name: 'articles')]
    public function __invoke(int $limit, int $offset): array
    {
        return $this->articleProvider->list($limit, $offset);
    }
}

Mutation

  • #[Mutation(name: 'name')]
use TheCodingMachine\GraphQLite\Annotations\Mutation;

class CreateArticleMutation
{
    public function __construct(
        private readonly ArticleCreator $articleCreator,
        private readonly ArticleProvider $articleProvider,
    ) {}

    #[Mutation(name: 'createArticle')]
    public function __invoke(ArticleInput $input): Article
    {
        $article = $this->articleCreator->create($input);
        return $this->articleProvider->get($article);
    }
}

Type

  • #[Type] + #[Field]
use App\Entity\Article as AppArticle;
use App\Entity\Tag as AppTag;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Annotations\Type;

#[Type]
class Article
{
    public function __construct(private readonly AppArticle $article) {}

    #[Field]
    public function id(): int
    {
        return $this->article->getId();
    }

    /**
     * @return Tag[]
     */
    #[Field]
    public function tags(): array
    {
        return array_map(fn (AppTag $tag) => new Tag($tag), $this->article->getTags());
    }
}

Input

  • #[Input] + #[Field]
use TheCodingMachine\GraphQLite\Annotations\Input;
use TheCodingMachine\GraphQLite\Annotations\Field;

#[Input]
class ArticleInput
{
    /**
     * @param int[] $tags
     */
    public function __construct(
        #[Field]
        public readonly string $title,
        #[Field]
        public readonly array $tags,
    ) {}
}

Entity argument

  • #[Entity] - by default generate Int! argument (you can specify property name by property arg.)
use App\Entity\Donald as AppDonald;
use App\GQL\Type\Donald;
use TheCodingMachine\GraphQLite\Annotations\Query;
use UXF\GQL\Attribute\Entity;

class DonaldQuery
{
    /**
     * @param Donald[] $donalds
     * @param Donald[]|null $donaldsNullable
     * @param Donald[]|null $donaldsNullableOptional
     * @param Donald[] $donaldsOptional
     */
    #[Query(name: 'donald')]
    public function __invoke(
        #[Entity] AppDonald $donald,
        #[Entity(property: 'uuid')] AppDonald $donald2,
        #[Entity(AppDonald::class)] array $donalds,
        #[Entity] ?AppDonald $donaldNullable,
        #[Entity(AppDonald::class)] ?array $donaldsNullable,
        #[Entity] ?AppDonald $donaldNullableOptional = null,
        #[Entity(AppDonald::class)] ?array $donaldsNullableOptional = null,
        #[Entity(AppDonald::class)] array $donaldsOptional = [],
    ): Donald {
        return new Donald($donald);
    }
}
type Query {
    donald(
        donald: Int!,
        donald2: String!,
        donalds: [Int!]!,
        donaldNullable: Int,
        donaldsNullable: [Int!],
        donaldNullableOptional: Int = null,
        donaldsNullableOptional: [Int!] = null,
        donaldsOptional: [Int!]! = []
    ): Donald!
}

Autowire

  • #[Autowire]
use App\Entity\Article as AppArticle;
use App\Entity\Tag as AppTag;
use TheCodingMachine\GraphQLite\Annotations\Autowire;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Annotations\Type;

#[Type]
class Article
{
    ...

    /**
     * @return Tag[]
     */
    #[Field]
    public function tags(#[Autowire] SomeService $service): array
    {
        return $someService->get();
    }
}

Inject

  • #[Inject]
use App\GQL\Type\Article;
use TheCodingMachine\GraphQLite\Annotations\Query;
use UXF\GQL\Attribute\Inject;

class ArticlesQuery
{
    #[Query(name: 'article')]
    public function __invoke(#[Inject] Coyote $coyote): Article
    {
        ...
    }
}
// config/packages/uxf.php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
    $containerConfigurator->extension('uxf_gql', [
        'injected' => [
            // class => resolver
            Coyote::class => CoyoteResolver::class,
            // btw Symfony Request is autoregistered 
        ],
    ]);

    $containerConfigurator->services()
        ->set(CoyoteResolver::class);
};
class CoyoteResolver
{
    public function __invoke(ReflectionNamedType $type, Request $request): Coyote
    {
        ...
    }
}

Authentication + Authorization

Allowed in Query and Mutation.

  • #[Logged] check logged user (only Q+M)
  • #[Right(Role::USER_ROOT)] check user role (only Q+M)
  • #[InjectUser] inject current user (Q+M+T)
use TheCodingMachine\GraphQLite\Annotations\Logged;
use TheCodingMachine\GraphQLite\Annotations\Mutation;
use TheCodingMachine\GraphQLite\Annotations\Right;

class CreateArticleMutation
{
    #[Logged]
    #[Right(Role::USER_ROOT)]
    #[Mutation(name: 'createArticle')]
    public function __invoke(#[InjectUser] User $user, ArticleInput $input): Article
    {
        ...
    }
}

ResponseCallbackModifier

Allow modify final response.

class LoginMutation
{
    public function __construct(private readonly ResponseCallbackModifier $responseModifier)
    {
    }

    #[Mutation(name: 'login')]
    public function __invoke(string $username, string $password): bool
    {
        $this->responseModifier->add(
            fn (Response $response) => $response->headers->set('Auth-Token', '1')
        );
        return true;
    }
}

Tools

  • bin/console uxf:gql-gen generate .graphql file

List of all controllers

$services->set(Service::class)
    ->arg('$controllerNames', param('uxf_gql.controller_names'));

External packages and permissions

Autocomplete (uxf/cms) + DataGrid (uxf/datagrid)

use UXF\Core\Contract\Permission\PermissionChecker;

final readonly class AppPermissionChecker implements PermissionChecker
{
    public function __construct(private Security $security)
    {
    }

    public function isAllowed(string $resourceType, string $resourceName): bool
    {
        // $resourceType - grid | autocomplete
        // $resourceName - gridName | autocompleteName

        return $this->security->isGranted('ROLE_ROOT');
    }
}

// services.php
return static function (ContainerConfigurator $container): void {
    $container->services()->set(PermissionChecker::class, AppPermissionChecker::class);
}