🎻

Symfony + API PlatformでGraphQL APIを実装する方法

2021/12/24に公開

メリークリスマスイブ!

Symfony Advent Calendar 2021 の24日目の記事です!🎄🌙

ちなみに、僕はよく TwitterにもSymfonyネタを呟いている ので、よろしければぜひ フォローしてやってください🕊🤲

昨日は @ippey_s さんの Symfony版Livewireこと、Live Componentさん でした✨

API Platformとは

API Platform はSymfony + DoctrineベースのオープンソースAPIフレームワークで、簡単な設定を書くだけでSymfonyアプリにREST APIやGraphQL APIを実装することができる便利なやつです。

今回はSymfony + API PlatformでGraphQL APIを実装する手順を順を追って解説してみたいと思います👍

なお、この記事で実装したコードは以下のリポジトリで公開していますので、ぜひあわせてご参照ください。

https://github.com/ttskch/symfony-api-platform-graphql-example

1. まずはSymfonyアプリを作成

記事執筆時点(2021/12/22時点)で API PlatformがまだSymfony 6に対応していない ので、今回はSymfony 5を使用します。

$ composer create-project symfony/skeleton:^5.0 symfony-api-platform-graphql-example
$ cd symfony-api-platform-graphql-example

2. API Platformをインストール

$ composer require api -n # 確認なしでレシピを実行

この時点で、Symfonyアプリを起動して /api にアクセスすれば、Swagger UIで書かれたREST APIドキュメントを見ることができます。(まだ内容は空ですが)

$ symfony serve -d
$ open -a "Google Chrome" http://localhost:8000/api

GraphQLに対応させるには少し追加の設定が必要なのですが、まずは一旦REST APIの動作を確認しながら基本的な準備を進めていくことにしましょう✋

3. エンティティを作る

ではまず何か適当なエンティティを作ってみましょう。

API Platformの依存としてDoctrineはすでに一式インストールされているので、.envDATABASE_URL を設定するだけですぐにDBを利用できます。今回はSQLiteを使うことにします。

# .env

- # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
+ DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
  # DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
- DATABASE_URL="postgresql://symfony:ChangeMe@127.0.0.1:5432/app?serverVersion=13&charset=utf8"
+ # DATABASE_URL="postgresql://symfony:ChangeMe@127.0.0.1:5432/app?serverVersion=13&charset=utf8"
$ bin/console doctrine:database:create

これでDBは開通なので、次は実際にエンティティを作ります。

楽をしたいので MakerBundle を入れておきましょう。

$ composer require maker

make コマンドを使ってエンティティを作ります。ここでは「ブログ投稿」を表す Post というエンティティを作ってみましょう。

$ bin/console make:entity Post

 created: src/Entity/Post.php
 created: src/Repository/PostRepository.php

 # 略

 New property name (press <return> to stop adding fields):
 > title

 Field type (enter ? to see all types) [string]:
 > text

 Can this field be null in the database (nullable) (yes/no) [no]:
 >

 updated: src/Entity/Post.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 > body

 Field type (enter ? to see all types) [string]:
 > text

 Can this field be null in the database (nullable) (yes/no) [no]:
 >

 updated: src/Entity/Post.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 > published

 Field type (enter ? to see all types) [string]:
 > boolean

 Can this field be null in the database (nullable) (yes/no) [no]:
 > yes

 updated: src/Entity/Post.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 > date

 Field type (enter ? to see all types) [string]:
 > date

 Can this field be null in the database (nullable) (yes/no) [no]:
 >

 updated: src/Entity/Post.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 >

  Success!

これで、以下の4つのプロパティを持つ Post エンティティができました。

プロパティ名 用途
$title text タイトル
$body text 本文
$published boolean 公開済みフラグ
$date date 投稿日

DBに反映します。

$ bin/console doctrine:migrations:diff
$ bin/console doctrine:migrations:migrate -n

最後に、エンティティをAPI Platformに認識させるため、以下のように @ApiResource() アノテーションを付加します。

今回はSymfony 5の流儀に従ってアトリビュートではなくアノテーションで書きます🙏

+ use ApiPlatform\Core\Annotation\ApiResource;
  use App\Repository\PostRepository;
  use Doctrine\ORM\Mapping as ORM;
  
  /**
   * @ORM\Entity(repositoryClass=PostRepository::class)
+  * @ApiResource()
   */
  class Post

この状態で /api の画面をリロードしてみると、以下のように Post エンティティのCRUDのAPIが作成されていることが分かります。便利!

試しに、REST API経由で実際にデータを作成してみましょう。

動いていますね!

4. GraphQL APIを有効にする

REST APIで動作確認するのはここまでにして、ここからはGraphQL APIを作っていきましょう。

API PlatformはREST APIだけでなく GraphQL APIにも対応しています

アプリケーションに webonyx/graphql-php をインストールすれば、それだけでGraphQL APIが有効になります。

$ composer require webonyx/graphql-php

これで、/api/graphqlGraphiQL が、/api/graphql/graphql_playgroundGraphQL Playground が使えるようになります。

もしこれらのIDEが不要な場合は、以下のようにして無効にすることもできます。

# api/config/packages/api_platform.yaml
api_platform:
    graphql:
        graphiql:
            enabled: false
        graphql_playground:
            enabled: false

試しにGraphiQLを使って実際に Post の一覧を取得してみましょう。

問題なく取得できていますね!

5. オペレーションを自作する(Resolver

デフォルトでは基本的なCRUDに必要な以下のオペレーションがすべて有効になります。(参考

  • Query
    • item_query
    • collection_query
  • Mutation
    • create
    • update
    • delete

特定のオペレーションだけを有効にしたい場合は、以下のようにエンティティの @ApiResource() アノテーション内で有効にしたいオペレーションを明示します。

/**
 * @ORM\Entity(repositoryClass=PostRepository::class)
 * @ApiResource(
 *     graphql={"item_query", "create"}
 * )
 */
class Post

こうすると item_querycreate 以外のオペレーションは無効になります。

また、より複雑なAPIを実装する場合、オペレーションを自作することも必要になってくるでしょう。このような場合には、Resolver と呼ばれるクラスを作り、そこにオペレーションの内容を記述します。

ここでは例として、create オペレーションをカスタマイズして、投稿作成時に date 引数が省略された場合には Post::date を自動でセットするようにしてみましょう。

このような処理はAPIのレイヤーで行うよりDoctrineのレイヤーで永続化の直前に行うほうが一般的かつ適切だと思いますが、他に適当な例が思いつかなかったのでここでは気にせず受け入れてください🙏

まず、以下のような Resolver クラスを作成します。

<?php
// src/ApiPlatform/GraphQL/Resolver/Post/CreateResolver.php

namespace App\ApiPlatform\GraphQL\Resolver\Post;

use ApiPlatform\Core\GraphQl\Resolver\MutationResolverInterface;
use App\Entity\Post;

class CreateResolver implements MutationResolverInterface
{
    /**
     * @param Post|null $post
     */
    public function __invoke($post, array $context): ?Post
    {
        if (!$post instanceof Post) {
            return null;
        }

        // $context['args'] にオペレーションに渡された引数が入っている
        $post->setDate(new \DateTime($context['args']['input']['date'] ?? 'today'));

        return $post;
    }
}

そして、Post エンティティの @ApiResource() アノテーションを以下のように変更します。

/**
 * @ORM\Entity(repositoryClass=PostRepository::class)
 * @ApiResource(
 *     graphql={
 *         "item_query",
 *         "collection_query",
 *         "create"={
 *             "mutation"=CreateResolver::class,
 *             "args"={
 *                 "title"={
 *                     "type"="String!",
 *                 },
 *                 "body"={
 *                     "type"="String!",
 *                 },
 *                 "published"={
 *                     "type"="Boolean",
 *                 },
 *                 "date"={
 *                     "type"="String",
 *                 },
 *             },
 *         },
 *         "update",
 *         "delete",
 *     }
 * )
 */
class Post
  • デフォルトの5つのオペレーションを明示(create だけしか書かないと他のオペレーションが無効になってしまうので)
  • create オペレーションを CreateResolver を使ったMutationとして定義
  • create オペレーションの引数を、date の型だけをデフォルトの String! から String(任意)に変えて列挙(date だけしか書かないと他の引数が無効になってしまうので)

ということをやっています。

では、実際に date 引数を指定せずに create オペレーションを実行してみましょう。

実際のオペレーション名には、API Platformによって末尾にエンティティ名(ここでは Post)が付加されます。

具体的なインターフェースはGraphiQLの右カラムに表示されているドキュメントから知ることができます。

期待どおりに動きましたね!(記事執筆時点の日付が2021/12/22なので、date が自動的に2021/12/22になっています)

mutation だけでなく、item_querycollection_query の自作もほぼ同様の手順で対応可能です。

また、もちろん今回のようにデフォルトのオペレーションを上書きするだけでなく、新たなオペレーションを追加することも可能です。

詳細は公式ドキュメントの以下の箇所あたりをご参照ください。

6. アノテーションではなくYAMLで設定する

ところで、Post エンティティの @ApiResource() アノテーションがすでに長すぎて可読性が低いので、ここらでアノテーションではなくYAMLで設定するように変更しておきましょう✋

アノテーションをごっそり削除して、

  /**
   * @ORM\Entity(repositoryClass=PostRepository::class)
-  * @ApiResource(
-  *     graphql={
-  *         "item_query",
-  *         "collection_query",
-  *         "create"={
-  *             "mutation"=CreateResolver::class,
-  *             "args"={
-  *                 "title"={
-  *                     "type"="String!",
-  *                 },
-  *                 "body"={
-  *                     "type"="String!",
-  *                 },
-  *                 "published"={
-  *                     "type"="Boolean",
-  *                 },
-  *                 "date"={
-  *                     "type"="String",
-  *                 },
-  *             },
-  *         },
-  *         "update",
-  *         "delete",
-  *     }
-  * )
   */
  class Post

代わりに config/packages/api_platform/post.yaml といったファイルに以下のようにYAML形式で記述します。

App\Entity\Post:
  graphql:
     item_query: ~
     collection_query: ~
     create:
       mutation: App\ApiPlatform\GraphQL\Resolver\Post\CreateResolver
       args:
         title:
           type: String!
         body:
           type: String!
         published:
           type: Boolean
         date:
           type: String
     update: ~
     delete: ~

そして、config/packages/api_platform.yaml で以下のようにこのYAMLファイルを読み込むようにします。

  api_platform:
      mapping:
-         paths: ['%kernel.project_dir%/src/Entity']
+         paths:
+             - '%kernel.project_dir%/src/Entity'
+             - '%kernel.project_dir%/config/packages/api_platform'

これで、@ApiResource() アノテーションに加えて config/packages/api_platform/ 配下のYAMLファイルでもAPIリソースを定義できるようになりました。

7. クエリの結果セットをカスタムする(DataProvider

オペレーションの自作だけでなく、クエリの結果セットをカスタムしたくなることも多々あります。このような場合には、DataProvider と呼ばれるクラスを作り、そこに結果セットの構築処理を記述します。

例として、投稿の一覧取得では 公開済みフラグが true のものだけしか出力しない ようにしてみましょう。

以下のような DataProvider クラスを作成すれば、supports() メソッドの働きによって自動でAPIの挙動に適用されます。(ただし、後述しますがこの時点では実装として未完成です)

<?php
// src/ApiPlatform/DataProvider/PostCollectionDataProvider.php

namespace App\ApiPlatform\DataProvider;

use ApiPlatform\Core\DataProvider\ArrayPaginator;
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Entity\Post;
use App\Repository\PostRepository;

class PostCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
{
    public function __construct(private PostRepository $repository)
    {
    }

    public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
    {
        return $resourceClass === Post::class;
    }

    public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable
    {
        $array = $this->repository->createQueryBuilder('p')
            ->andWhere('p.published = 1')
            ->getQuery()
            ->getResult()
        ;

        // 戻り値は \ApiPlatform\Core\DataProvider\PartialPaginatorInterface の実装である必要がある
        return new ArrayPaginator($array, 0, count($array));
    }
}

collection_query オペレーションを実行してみると、

投稿自体は現在2件存在しているはずですが、公開済みの投稿が存在しないため結果が0件となっています👌

update オペレーションを使って2件目の投稿のみ公開済みに変更してみましょう。

これで再度 collection_query オペレーションを実行してみると、

確かに公開済みにした1件だけが出力されました👌

8. 自作した DataProvider でページネーションやフィルタなどの基本機能を有効にする

ところで、実は先ほどの PostCollectionDataProvider の実装では、デフォルトでは特に何も考えなくても使えていた ページネーションやフィルタといった基本機能が使えなくなっています。

試しに collection_query オペレーションを (first: 0) (最初の0件を取得、つまり1件も取得しない)という引数付きで実行してみると、

普通に1件が取得されてしまってページネーションが効いていないことが分かります。

実はAPI Platformでは(デフォルトの実装も含めた)DataProvider 用の基本機能が Extension という形でモジュール化されており、DataProvider を自作した場合はデフォルトで用意されている Extension を明示的に適用してあげる必要があります。(参考

具体的には、以下のようにコンストラクタインジェクションで iterable $collectionExtensions を受け取って、それらを順に適用していく、というコードを追記します。

「適用する」手順については特に深く考えずにドキュメントのとおりに書いても動きますが、詳しく知りたい方は以下のデフォルトの実装を見ると参考になるかと思います。

  <?php
  
  namespace App\ApiPlatform\DataProvider;
  
+ use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
+ use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
  use ApiPlatform\Core\DataProvider\ArrayPaginator;
  use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
  use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
  use App\Entity\Post;
  use App\Repository\PostRepository;
  
  class PostCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
  {
-     public function __construct(private PostRepository $repository)
-     {
-     }
+     public function __construct(
+         private PostRepository $repository,
+         private iterable $collectionExtensions,
+     ) {}
  
      public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
      {
          return $resourceClass === Post::class;
      }
  
      public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable
      {
-         $array = $this->repository->createQueryBuilder('p')
+         $qb = $this->repository->createQueryBuilder('p')
              ->andWhere('p.published = 1')
-             ->getQuery()
-             ->getResult()
          ;
  
+         $queryNameGenerator = new QueryNameGenerator();
+         foreach ($this->collectionExtensions as $extension) {
+             $extension->applyToCollection($qb, $queryNameGenerator, $resourceClass, $operationName, $context);
+ 
+             if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) {
+                 return $extension->getResult($qb);
+             }
+         }
+ 
+         $array = $qb->getQuery()->getResult();
+ 
          return new ArrayPaginator($array, 0, count($array));
      }
  }

コンストラクタ引数の iterable $collectionExtensionsconfig/services.yaml で以下のように明示的に渡してあげる必要があります。

services:
    App\ApiPlatform\DataProvider\PostCollectionDataProvider:
        arguments:
            $collectionExtensions: !tagged api_platform.doctrine.orm.query_extension.collection

これもドキュメントに書かれているとおりですが、基本機能の Extension はすべて api_platform.doctrine.orm.query_extension.collection でタグ付けされているので、何も考えずにこれらをまとめて注入してあげればよいというわけです。

これで、無事にページネーション(等の基本機能)が動作するようになりました👍

ちなみに、複数のエンティティについて一覧系の DataProvider を自作する場合、すべての DataProvider に「Extension を適用する処理」を書かなければならないので、以下のような感じで Trait にしておくと便利だと思います。

<?php
// src/ApiPlatform/DataProvider/Traits/CollectionDataProviderTrait.php

namespace App\ApiPlatform\DataProvider\Traits;

use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
use ApiPlatform\Core\DataProvider\ArrayPaginator;
use Doctrine\ORM\QueryBuilder;

/**
 * @property iterable<QueryCollectionExtensionInterface> $collectionExtensions
 */
trait CollectionDataProviderTrait
{
    protected function getResult(QueryBuilder $qb, string $resourceClass, string $operationName = null, array $context = []): iterable
    {
        $queryNameGenerator = new QueryNameGenerator();

        foreach ($this->collectionExtensions as $extension) {
            $extension->applyToCollection($qb, $queryNameGenerator, $resourceClass, $operationName, $context);

            if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) {
                return $extension->getResult($qb);
            }
        }

        $array = $qb->getQuery()->getResult();

        return new ArrayPaginator($array, 0, count($array));
    }
}
  <?php
  
  namespace App\ApiPlatform\DataProvider;
  
- use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
- use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
- use ApiPlatform\Core\DataProvider\ArrayPaginator;
  use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
  use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
+ use App\ApiPlatform\DataProvider\Traits\CollectionDataProviderTrait;
  use App\Entity\Post;
  use App\Repository\PostRepository;
  
  class PostCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
  {
+     use CollectionDataProviderTrait;
+ 
      public function __construct(
          private PostRepository $repository,
          private iterable $collectionExtensions,
      ) {}
  
      public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
      {
          return $resourceClass === Post::class;
      }
  
      public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable
      {
          $qb = $this->repository->createQueryBuilder('p')
              ->andWhere('p.published = 1')
          ;
  
-         $queryNameGenerator = new QueryNameGenerator();
-         foreach ($this->collectionExtensions as $extension) {
-             $extension->applyToCollection($qb, $queryNameGenerator, $resourceClass, $operationName, $context);
- 
-             if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) {
-                 return $extension->getResult($qb);
-             }
-         }
- 
-         $array = $qb->getQuery()->getResult();
- 
-         return new ArrayPaginator($array, 0, count($array));
+         return $this->getResult($qb, $resourceClass, $operationName, $context);
      }
  }

また、collection_query だけでなく item_query のカスタマイズもほぼ同様の手順で対応可能です。

詳細は(既出ですが)下記の公式ドキュメントをご参照ください。

https://api-platform.com/docs/core/data-providers/

9. 一覧取得APIに絞り込みと並べ替えを実装する

collection_query の結果セットを絞り込んだり並べ替えたりできる フィルタ という機能があります。

例えば、投稿一覧について

  • 投稿日で絞り込めるように
  • 投稿日で並べ替えられるように

するには、以下のようにすればよいです。

まず、絞り込み用と並べ替え用のフィルタをそれぞれサービスとして定義します。

# config/services.yaml

services:
    api.post.date_filter:
        parent: api_platform.doctrine.orm.date_filter
        arguments: [{date: ~}]
        tags: [api_platform.filter]
        autowire: false
        autoconfigure: false
    api.post.order_filter:
        parent: api_platform.doctrine.orm.order_filter
        arguments:
            $properties: {date: ~}
            $orderParameterName: order
        tags: [api_platform.filter]
        autowire: false
        autoconfigure: false

そして、config/packages/api_platform/post.yaml に以下のように設定を追加します。

  App\Entity\Post:
+   attributes:
+     filters:
+       - api.post.date_filter
+       - api.post.order_filter
    graphql:
      # 略

あるいは、YAMLとアノテーションに設定が分散することを許容するなら(または設定をすべてアノテーションで書くなら)、わざわざサービスを定義せずに以下のようにアノテーションを2行書くだけでも同じ設定が可能です。

+ use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
+ use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
 
  /**
   * @ORM\Entity(repositoryClass=PostRepository::class)
+  * @ApiFilter(DateFilter::class, properties={"date"})
+  * @ApiFilter(OrderFilter::class, properties={"date"}, arguments={"orderParameterName"="order"})
   */
  class Post

(いくつか公開済みの投稿を増やした上で)実行すると以下のような感じになります👌

詳細は下記の公式ドキュメントをご参照ください。

ドキュメントの間違い?

ちなみに、https://api-platform.com/docs/core/graphql/#filters を見ると

App\Entity\Post:
  attributes:
    filters:
      - api.post.date_filter
      - api.post.order_filter

ではなく

App\Entity\Post:
  graphql:
    collection_query:
      filters:
        - api.post.date_filter
        - api.post.order_filter

とすることで、REST APIや他のオペレーションに波及させずにGraphQL APIの collection_query にだけフィルタを適用することができるようなことが書いてありますが、記事執筆時点ではこの書き方だと期待どおり動作しません🤔

あたりで ApiPlatform\Core\Metadata\Resource\ResourceMetadata::getCollectionOperationAttribute() を呼んでいますが、その先で呼ばれる ApiPlatform\Core\Metadata\Resource\ResourceMetadata::findOperationAttribute() の実装 を見る限り、APIリソース設定の 'graphql' キーに対する設定値は別の変数に格納されていて ここでは使用されないように見えます。

IssueやPRも見つけられず、バグなのか仕様なのかもよく分かっていません😓もし詳しい方いらっしゃったらぜひ 教えていただけると 嬉しいです🙏

10. REST APIを無効にする

GraphQL APIだけを有効にしてREST APIは無効にしておきたいという場合も多いと思いますが、残念ながらシュッと設定する方法は用意されておらず、

  • 有効なエンドポイントをアイテムのGETとコレクションのGETのみにする
  • それぞれのレスポンスを常に404にする

という泥臭い設定をする必要があります。

  api_platform:
      mapping:
          paths:
              - '%kernel.project_dir%/src/Entity'
              - '%kernel.project_dir%/config/packages/api_platform'
      patch_formats:
          json: ['application/merge-patch+json']
      swagger:
          versions: [3]
+ 
+     defaults:
+         item_operations:
+             get:
+                 controller: ApiPlatform\Core\Action\NotFoundAction
+                 read: false
+                 output: false
+         collection_operations:
+             get:
+                 controller: ApiPlatform\Core\Action\NotFoundAction
+                 read: false
+                 output: false

参考:https://github.com/api-platform/core/issues/2796#issuecomment-606729715

おまけ:ログインユーザーごとにアクセス可否を制御

Securityコンポーネントを導入してログインユーザーごとにアクセス可否を制御したい場合は、APIリソース設定に security というキーが用意されていて、お馴染みの is_granted などが使えるので、通常のSymfonyアプリを作るときと同じ要領で対応可能です👍

App\Entity\Post:
  graphql:
    item_query:
      security: is_granted('VIEW', object) # Postエンティティ用のSecurity VoterでVIEWという権限が実装されているイメージ
    collection_query:
      security: is_granted('ROLE_USER')

詳細は下記の公式ドキュメントをご参照ください。

おわりに

というわけで、Symfony + API Platform でGraphQL APIを実装する手順をまあまあ細かく解説してみました!

これだけ知っていれば基本的なGraphQL APIはまあだいたい実装できるのではないかと思います。よろしければ参考にしてみてください!

Symfony Advent Calendar 2021、明日は @77web さんです!お楽しみに!

GitHubで編集を提案

Discussion