PHPackages                             monkeyscloud/monkeyslegion-graphql - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. monkeyscloud/monkeyslegion-graphql

ActiveLibrary[API Development](/categories/api)

monkeyscloud/monkeyslegion-graphql
==================================

Code-first GraphQL server for the MonkeysLegion framework — PHP 8.4 attributes, PSR-15, DataLoader, subscriptions, and security out of the box.

2.0.0(2mo ago)10MITPHPPHP ^8.4

Since Jul 23Pushed 2mo agoCompare

[ Source](https://github.com/MonkeysCloud/MonkeysLegion-GraphQL)[ Packagist](https://packagist.org/packages/monkeyscloud/monkeyslegion-graphql)[ Docs](https://monkeyslegion.com/docs/packages/graphql)[ RSS](/packages/monkeyscloud-monkeyslegion-graphql/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (14)Versions (4)Used By (0)

MonkeysLegion-GraphQL
=====================

[](#monkeyslegion-graphql)

**Code-first GraphQL server for the MonkeysLegion framework** — PHP 8.4 attributes, PSR-15, DataLoader, subscriptions, and security out of the box.

Requirements
------------

[](#requirements)

- PHP 8.4+
- `webonyx/graphql-php` ^15.30

Installation
------------

[](#installation)

```
composer require monkeyscloud/monkeyslegion-graphql
```

The `GraphQLProvider` is auto-registered via `composer.json` extra.

Quick Start
-----------

[](#quick-start)

### 1. Define a Type

[](#1-define-a-type)

```
use MonkeysLegion\GraphQL\Attribute\{Type, Field};

#[Type(description: 'A user')]
final class UserType
{
    #[Field]
    public function id(User $root): int
    {
        return $root->id;
    }

    #[Field]
    public function name(User $root): string
    {
        return $root->name;
    }

    #[Field(description: 'Email address')]
    public function email(User $root): string
    {
        return $root->email;
    }
}
```

### 2. Define a Query

[](#2-define-a-query)

```
use MonkeysLegion\GraphQL\Attribute\{Query, Arg};
use MonkeysLegion\GraphQL\Context\GraphQLContext;

#[Query(name: 'user', description: 'Get user by ID')]
final class GetUserQuery
{
    public function __construct(private UserRepository $users) {}

    public function __invoke(
        mixed $root,
        #[Arg(description: 'User ID')] int $id,
        GraphQLContext $context,
    ): ?User {
        return $this->users->find($id);
    }
}
```

### 3. Define a Mutation

[](#3-define-a-mutation)

```
use MonkeysLegion\GraphQL\Attribute\{Mutation, Arg};

#[Mutation(name: 'createUser', description: 'Create a new user')]
final class CreateUserMutation
{
    public function __construct(private UserRepository $users) {}

    public function __invoke(
        mixed $root,
        #[Arg] string $name,
        #[Arg] string $email,
    ): User {
        return $this->users->create($name, $email);
    }
}
```

### 4. Configure

[](#4-configure)

```
# config/graphql.mlc
graphql:
  endpoint: /graphql
  scan:
    directories:
      - app/GraphQL
  security:
    max_depth: 10
    max_complexity: 200
```

Features
--------

[](#features)

### Attributes

[](#attributes)

AttributeTargetPurpose`#[Type]`ClassGraphQL object type`#[Field]`Method/PropertyObject type field`#[Query]`ClassRoot query field`#[Mutation]`ClassRoot mutation field`#[Subscription]`ClassSubscription field`#[Arg]`ParameterArgument metadata`#[InputType]`ClassInput object type`#[Enum]`Backed enumEnum type`#[InterfaceType]`Class/InterfaceInterface type`#[UnionType]`ClassUnion type`#[Middleware]`Class/MethodPer-field middleware### Custom Scalars

[](#custom-scalars)

- `DateTime` — ISO 8601 serialization
- `JSON` — Arbitrary JSON passthrough
- `Email` — Email format validation
- `URL` — URL format validation
- `Upload` — Multipart file upload

### Security

[](#security)

```
graphql:
  security:
    max_depth: 10          # Query depth limiting
    max_complexity: 200    # Field cost analysis
    introspection: false   # Disable introspection in production
    persisted_queries: true # APQ with SHA256
    rate_limit:
      enabled: true
      max_requests: 100
      window_seconds: 60
```

### DataLoader (N+1 Prevention)

[](#dataloader-n1-prevention)

```
use MonkeysLegion\GraphQL\Loader\DataLoader;

final class UserLoader extends DataLoader
{
    public function __construct(private UserRepository $users) {}

    protected function batchLoad(array $keys): array
    {
        $users = $this->users->findByIds($keys);
        return array_map(
            fn(int $id) => $users[$id] ?? null,
            $keys,
        );
    }
}
```

### Relay Pagination

[](#relay-pagination)

```
use MonkeysLegion\GraphQL\Type\ConnectionType;

// Automatically creates UserConnection, UserEdge, PageInfo types
$connectionType = ConnectionType::create('User', $userType);
```

### Subscriptions

[](#subscriptions)

```
use MonkeysLegion\GraphQL\Attribute\Subscription;

#[Subscription(name: 'messageAdded', description: 'New message')]
final class MessageAddedSubscription
{
    public function __invoke(mixed $root): Message
    {
        return $root;
    }
}
```

Supports `graphql-ws` protocol with in-memory and Redis PubSub backends.

### File Uploads

[](#file-uploads)

Follows the [GraphQL multipart request spec](https://github.com/jaydenseric/graphql-multipart-request-spec):

```
#[Mutation(name: 'uploadFile')]
final class UploadFileMutation
{
    public function __invoke(
        mixed $root,
        #[Arg] UploadedFileInterface $file,
    ): string {
        $file->moveTo('/uploads/' . $file->getClientFilename());
        return $file->getClientFilename();
    }
}
```

CLI Commands
------------

[](#cli-commands)

CommandDescription`php ml graphql:schema:dump`Dump schema as SDL`php ml graphql:schema:validate`Validate schema`php ml graphql:cache:warm`Warm schema cache`php ml graphql:cache:clear`Clear schema cache`php ml graphql:introspect`Dump introspection JSONEntity Integration
------------------

[](#entity-integration)

When `monkeyslegion-entity` is installed, you can auto-map your entities to GraphQL types without writing boilerplate type classes.

### Entity Example

[](#entity-example)

```
use MonkeysLegion\Entity\Attribute\Entity;
use MonkeysLegion\Entity\Attribute\Id;
use MonkeysLegion\Entity\Attribute\Column;

#[Entity(table: 'products')]
class Product
{
    #[Id]
    public int $id;

    #[Column(type: 'varchar', length: 255)]
    public string $name;

    #[Column(type: 'text')]
    public string $description;

    #[Column(type: 'decimal')]
    public float $price;

    #[Column(type: 'boolean')]
    public bool $active;

    #[Column(type: 'datetime')]
    public \DateTimeImmutable $createdAt;
}
```

### Auto-Map Entities to GraphQL Types

[](#auto-map-entities-to-graphql-types)

```
use MonkeysLegion\GraphQL\Scanner\EntityTypeMapper;

$mapper = new EntityTypeMapper();

// Maps all typed properties → GraphQL fields automatically:
//   int    → Int!        float  → Float!
//   string → String!     bool   → Boolean!
//   DateTime* → DateTime!  (custom scalar)
//   ?string → String     (nullable)
$typeConfig = $mapper->map(Product::class);
// Returns: ['name' => 'Product', 'fields' => ['id' => ..., 'name' => ..., ...]]

// Map multiple entities at once
$types = $mapper->mapAll([Product::class, Category::class, Order::class]);
```

### Auto-Generated CRUD Resolvers

[](#auto-generated-crud-resolvers)

Use `EntityResolver` to expose entities without manual resolver classes:

```
use MonkeysLegion\GraphQL\Resolver\EntityResolver;
use MonkeysLegion\GraphQL\Type\ConnectionType;

// Single entity by ID
//   query { product(id: 42) { name price } }
$findProduct = EntityResolver::findById(
    Product::class,
    ProductRepository::class, // optional — defaults to Product::class . 'Repository'
);

// List all entities
//   query { products { name price active } }
$listProducts = EntityResolver::findAll(Product::class);

// Relay-style pagination with cursors
//   query { products(first: 10, after: "Y3Vyc29yOjk=") {
//     edges { node { name } cursor }
//     pageInfo { hasNextPage endCursor }
//     totalCount
//   }}
$paginatedProducts = EntityResolver::connection(Product::class);
```

### Full Example: Entity-Backed Schema

[](#full-example-entity-backed-schema)

```
use MonkeysLegion\GraphQL\Attribute\{Type, Field, Query, Arg};
use MonkeysLegion\GraphQL\Context\GraphQLContext;

// 1. Define the GraphQL type wrapping the entity
#[Type(description: 'A product in the catalog')]
final class ProductType
{
    #[Field]
    public function id(Product $root): int { return $root->id; }

    #[Field]
    public function name(Product $root): string { return $root->name; }

    #[Field]
    public function price(Product $root): float { return $root->price; }

    #[Field(description: 'Active in store?')]
    public function active(Product $root): bool { return $root->active; }

    #[Field(description: 'ISO 8601')]
    public function createdAt(Product $root): string {
        return $root->createdAt->format('c');
    }
}

// 2. Query resolver using the repository from DI
#[Query(name: 'product', description: 'Find product by ID')]
final class GetProductQuery
{
    public function __construct(private ProductRepository $products) {}

    public function __invoke(
        mixed $root,
        #[Arg(description: 'Product ID')] int $id,
        GraphQLContext $context,
    ): ?Product {
        return $this->products->find($id);
    }
}

// 3. List with filtering
#[Query(name: 'products', description: 'List products')]
final class ListProductsQuery
{
    public function __construct(private ProductRepository $products) {}

    public function __invoke(
        mixed $root,
        #[Arg(nullable: true)] ?bool $active,
        #[Arg(nullable: true, defaultValue: 20)] int $limit,
    ): array {
        if ($active !== null) {
            return $this->products->findByActive($active, $limit);
        }
        return $this->products->findAll($limit);
    }
}
```

Route Registration
------------------

[](#route-registration)

`GraphQLProvider` automatically registers routes with `monkeyslegion-router` when the application boots.

### Default Routes

[](#default-routes)

MethodPathHandlerDescription`POST``/graphql``GraphQLMiddleware`Queries &amp; mutations`GET``/graphql``GraphQLMiddleware`Simple GET queries`GET``/graphiql``GraphiQLMiddleware`Interactive IDE (dev)### Configuration

[](#configuration)

```
# config/graphql.mlc
graphql:
  endpoint: /graphql            # Change the endpoint path
  graphiql_enabled: true        # Disable GraphiQL in production
  graphiql_endpoint: /graphiql  # Custom GraphiQL path
  debug: false                  # Enable for detailed error traces
  scan_dirs:
    - app/GraphQL               # Where to find Type/Query/Mutation classes
  scan_namespace: App\GraphQL   # PSR-4 namespace for scanned classes
  security:
    max_depth: 10
    max_complexity: 200
    introspection: true         # Disable in production
    persisted_queries: false
    rate_limit:
      max_requests: 100
      window_seconds: 60
  cache:
    enabled: false
    ttl: 3600                   # Schema cache TTL in seconds
  subscriptions:
    enabled: false
    driver: memory              # 'memory' or 'redis'
    host: 0.0.0.0
    port: 6001
    redis_dsn: redis://127.0.0.1:6379
```

### How Route Registration Works

[](#how-route-registration-works)

The `GraphQLProvider::register()` method is called automatically during application bootstrap (via the `monkeyslegion` extra in `composer.json`). Here's what happens:

```
// This happens automatically — no manual setup needed.
// The provider:
//   1. Reads config/graphql.mlc
//   2. Registers all GraphQL services in the DI container
//   3. Registers routes with MonkeysLegion\Router\Router

// Routes are registered as closures that delegate to PSR-15 middleware:
$router->post('/graphql', $graphqlHandler, 'graphql');
$router->get('/graphql', $graphqlHandler, 'graphql.get');
$router->get('/graphiql', $graphiqlHandler, 'graphiql'); // if enabled
```

### Custom Route Middleware

[](#custom-route-middleware)

Stack your own middleware (auth, CORS, rate-limiting) alongside GraphQL:

```
use MonkeysLegion\GraphQL\Attribute\Middleware;

// Per-resolver middleware
#[Middleware('App\Middleware\AuthMiddleware')]
#[Middleware('App\Middleware\RateLimitMiddleware')]
#[Query(name: 'adminUsers')]
final class AdminUsersQuery
{
    public function __invoke(mixed $root, GraphQLContext $context): array
    {
        // Only reached if auth + rate-limit pass
        return $context->container->get(UserRepository::class)->findAdmins();
    }
}
```

### Testing the Endpoint

[](#testing-the-endpoint)

```
# Simple query
curl -X POST http://localhost:8080/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query": "{ product(id: 1) { name price } }"}'

# Mutation
curl -X POST http://localhost:8080/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query": "mutation { createProduct(name: \"Widget\", price: 9.99) { id name } }"}'

# GET request (simple queries only)
curl 'http://localhost:8080/graphql?query=\{products\{name\}\}'

# Open GraphiQL IDE in browser
open http://localhost:8080/graphiql
```

Facade
------

[](#facade)

```
use MonkeysLegion\GraphQL\GraphQL;

// Execute a query programmatically
$result = GraphQL::execute('{ user(id: 1) { name } }');

// Publish a subscription event
GraphQL::publish('messageAdded', $message);

// Get the built schema
$schema = GraphQL::schema();
```

License
-------

[](#license)

MIT — see [LICENSE](LICENSE) for details.

###  Health Score

39

—

LowBetter than 86% of packages

Maintenance83

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~202 days

Total

2

Last Release

88d ago

Major Versions

1.0.0 → 2.0.02026-02-11

### Community

Maintainers

![](https://www.gravatar.com/avatar/51e4df19377776baa8eafb605d9e7d2374b855c686f552c20d6856e94e3597c3?d=identicon)[yorchperaza](/maintainers/yorchperaza)

---

Top Contributors

[![yorchperaza](https://avatars.githubusercontent.com/u/2913369?v=4)](https://github.com/yorchperaza "yorchperaza (10 commits)")

---

Tags

graphqlopen-sourcephpphp-frameworkphp-libraryphpgraphqlpsr-15attributessubscriptionsdataLoadermonkeyslegioncode-first

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/monkeyscloud-monkeyslegion-graphql/health.svg)

```
[![Health](https://phpackages.com/badges/monkeyscloud-monkeyslegion-graphql/health.svg)](https://phpackages.com/packages/monkeyscloud-monkeyslegion-graphql)
```

###  Alternatives

[thecodingmachine/graphqlite

Write your GraphQL queries in simple to write controllers (using webonyx/graphql-php).

5723.1M30](/packages/thecodingmachine-graphqlite)[algolia/algoliasearch-client-php

API powering the features of Algolia.

69333.0M114](/packages/algolia-algoliasearch-client-php)[aimeos/ai-admin-graphql

Aimeos Admin GraphQL API extension

944100.0k4](/packages/aimeos-ai-admin-graphql)[rubix/server

Deploy your Rubix ML models to production with scalable stand-alone inference servers.

632.3k](/packages/rubix-server)[jaxon-php/jaxon-core

Jaxon is an open source PHP library for easily creating Ajax web applications

73142.3k25](/packages/jaxon-php-jaxon-core)[wordpress/php-ai-client

A provider agnostic PHP AI client SDK to communicate with any generative AI models of various capabilities using a uniform API.

26236.6k14](/packages/wordpress-php-ai-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
