Docs

GraphQL

MonkeysLegion GraphQL adds a full GraphQL stack to the MonkeysLegion framework:

What

How

HTTP endpoint

POST /graphql (also GET /graphql?query=…)

Type system

Pure PHP 8 attributes (#[Type], #[Query], #[Mutation], #[Subscription])

Dev tools

GraphiQL playground at /graphiql (dev only)

Real-time

WebSocket server on ws://<host>:6001 (subscriptions)

DI / PSR-15

Services are auto-bound through MonkeysLegion’s provider mechanism

 

Installation

composer require monkeyscloud/monkeyslegion-graphql

The package injects itself into

composer.json → extra.monkeyslegion.providers, so your bootstrap

automatically calls MonkeysLegion\GraphQL\GraphQL::register().

Quick Start

// app/GraphQL/Types/Post.php
namespace App\GraphQL\Types;

use MonkeysLegion\GraphQL\Attribute\Type;
use GraphQL\Type\Definition\{ObjectType, Type as GQL};

#[Type]
final class Post extends ObjectType
{
    public function __construct()
    {
        parent::__construct([
            'name'   => 'Post',
            'fields' => [
                'id'    => GQL::nonNull(GQL::id()),
                'title' => GQL::string(),
                'body'  => GQL::string(),
            ],
        ]);
    }
}
// app/GraphQL/Query/PostQuery.php
namespace App\GraphQL\Query;

use MonkeysLegion\GraphQL\Attribute\Query;
use App\Repository\PostRepository;

#[Query(name: 'posts')]
final class PostQuery
{
    public function __construct(private PostRepository $repo) {}

    public function __invoke(): array
    {
        return $this->repo->findAll();
    }
}

GET /graphiql → run:

{
  posts { id title }
}

Defining Schema Elements

Attribute

Extends / implements

Expected return value

#[Type]

GraphQL\Type\Definition\ObjectType

n/a

#[Query]

callable or ObjectType

Resolver result

#[Mutation]

callable or ObjectType

Resolver result

#[Subscription]

callable or ObjectType

Async iterator / generator

Example Mutation

#[Mutation(name: 'createPost')]
final class CreatePost
{
    public function __construct(private PostRepository $repo) {}

    public function __invoke(null $_, array $args): int
    {
        return $this->repo->insert($args['title'], $args['body']);
    }
}

Subscriptions

  1. Start the WebSocket server (CLI, Supervisor, or systemd):

use MonkeysLegion\GraphQL\WebSocket\SubscriptionServer;

SubscriptionServer::run(
    $container->get(SubscriptionManager::class),
    $container->get(PubSubInterface::class), // default InMemoryPubSub
);
  1. Define a subscription type:

#[Subscription('counter')]
class CounterSub extends ObjectType
{
    public function __construct(PubSubInterface $ps)
    {
        parent::__construct([
            'name'       => 'CounterSub',
            'fields'     => [ 'count' => ['type' => GQL::int()] ],
            'subscribe'  => fn() => $ps->subscribe('counter', fn($v)=>['count'=>$v]),
            'resolve'    => fn($root) => $root,
        ]);
    }
}
  1. Publish events anywhere in your app:

$pubsub->publish('counter', $value++);
  1. Client:

const ws = new WebSocket('ws://localhost:6001');
ws.onopen = () => ws.send(JSON.stringify({
  query: 'subscription { counter { count } }'
}));
ws.onmessage = e => console.log(JSON.parse(e.data));

Switching to Redis Pub/Sub

Swap the entry for PubSubInterface::class in your DI config to a Redis-backed implementation (community driver).

Key

Default

Description

graphql.playground

true in dev

Expose /graphiql

graphql.ws.port

6001

WebSocket port

graphql.ws.host

0.0.0.0

Bind address

graphql.pubsub

in_memory

in_memory or redis

Add these keys to config/graphql.mlc:

playground = env("GQL_PLAYGROUND", true)
ws.port    = 6001
ws.host    = "0.0.0.0"
pubsub     = "in_memory"

MlcConfig is loaded automatically by the provider.

CLI Helpers

php vendor/bin/ml graphql:subscriptions   # start WS server
php vendor/bin/ml graphql:schema:print    # dump SDL

Troubleshooting

Symptom

Fix

404 /graphiql

Set APP_ENV=dev or graphql.playground=true.

“Undefined class Loop”

You’re on React 0.4: use React\EventLoop\Factory::create() not Loop::get().

WebSocket closes immediately

Check browser console; ensure server port & scheme match.

Subscription resolves once then stops

Remember to call the unsubscribe callback inside your generator.

Roadmap

  • v0.3 – DataLoader batching, per-request cache

  • v0.4 – Relay cursor-based pagination helpers

  • v1.0 – Federation helpers, Apollo Gateway integration

Contributing

  1. git clone & composer install

  2. Run tests: vendor/bin/phpunit – must stay green.

  3. Follow PSR-12, add doc-blocks & tests.

  4. Open PRs against main.

MIT license – happy hacking! 🐒🚀