uxf/gql

维护者

详细信息

gitlab.com/uxf/gql

源代码

问题


README

安装

$ composer req uxf/gql

配置

// 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
    ]);
};

默认支持

  • 所有PHP原生类型(包括原生BackedEnum)
  • UXF\Core\Type\Date
  • UXF\Core\Type\DateTime
  • UXF\Core\Type\Time
  • Ramsey\Uuid\UuidInterface

用法

查询

  • #[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(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] + #[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] + #[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] - 默认生成 Int! 参数(您可以通过 property 参数指定属性名)
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]
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]
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
    {
        ...
    }
}

身份验证 + 授权

在查询和突变中允许。

  • #[Logged] 检查已登录用户(仅Q+M)
  • #[Right(Role::USER_ROOT)] 检查用户角色(仅Q+M)
  • #[InjectUser] 注入当前用户(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
    {
        ...
    }
}

响应回调修改器

允许修改最终响应。

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;
    }
}

工具

  • bin/console uxf:gql-gen 生成 .graphql 文件

外部包和权限

自动完成(uxf/cms)+ 数据网格(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);
}