PHPackages                             semitexa/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. semitexa/graphql

ActiveSemitexa-module[API Development](/categories/api)

semitexa/graphql
================

Semitexa GraphQL - opt-in GraphQL operation discovery built on Payload DTO, Handler, and typed output contracts

2026.05.08.1640(1mo ago)12[1 PRs](https://github.com/semitexa/semitexa-graphql/pulls)MITPHPPHP ^8.4

Since Apr 1Pushed 1w agoCompare

[ Source](https://github.com/semitexa/semitexa-graphql)[ Packagist](https://packagist.org/packages/semitexa/graphql)[ RSS](/packages/semitexa-graphql/feed)WikiDiscussions master Synced 4w ago

READMEChangelogDependencies (2)Versions (11)Used By (0)

Semitexa GraphQL
================

[](#semitexa-graphql)

A real, executable GraphQL runtime for Semitexa applications. Built on the same `Payload DTO → Handler → Resource` architecture as the rest of the framework. Powered by [`webonyx/graphql-php`](https://github.com/webonyx/graphql-php) under the hood, kept behind Semitexa-owned contracts so webonyx types do not leak across the codebase.

What this package provides
--------------------------

[](#what-this-package-provides)

- `#[ExposeAsGraphql]` attribute — opt a Semitexa Payload DTO into the GraphQL schema.
- A discovered, immutable registry of GraphQL operations (`GraphqlOperationRegistryInterface`).
- A schema builder that turns those operations into a webonyx `Schema` (`SchemaProviderInterface`).
- A runtime executor that parses, validates, and runs GraphQL queries through the existing Semitexa pipeline (`GraphqlExecutorInterface`).
- A predictable error envelope mapped from Semitexa domain exceptions (`GraphqlErrorMapper`).
- A framework-agnostic `GraphqlExecutionResult` DTO that any HTTP transport can serialize.
- The reusable `POST /graphql` HTTP route — `GraphqlEndpointPayload` (under `Semitexa\Graphql\Application\Payload\Request\`), `GraphqlEndpointHandler` (under `Semitexa\Graphql\Application\Handler\PayloadHandler\`) and `GraphqlEndpointResource` (under `Semitexa\Graphql\Application\Resource\Response\`) — declared with `#[AsPayload]` so it is auto-discovered as soon as the package is installed. No per-application wiring required. The route path is configurable per deployment through `.env` (see [Configuring the route path](#configuring-the-route-path)) without forking package code.

Application modules (e.g. a demo under `src/modules/`) only need to declare their domain operations with `#[AsPayload] + #[ExposeAsGraphql]`. The interactive runner page, demo handlers, and any application-specific schema authoring stay in the application — the package owns transport and runtime, the application owns the schema content. Playground does not own the GraphQL HTTP route.

The interactive demo at `GET /playground/graphql` (lives in Playground, not here) walks a developer through four real surfaces: the package's POST /graphql runner, the Resource DTO multi-profile route at `/playground/customers/{id}` (JSON / JSON-LD / GraphQL response), `?include=` lazy relation expansion, and the `?query=` selection-set bridge. All four sections fire real HTTP — none are mocked.

Configuring the route path
--------------------------

[](#configuring-the-route-path)

The default route is `POST /graphql`. The `path` argument on `GraphqlEndpointPayload`'s `#[AsPayload]` uses Semitexa's standard env-driven attribute-value syntax:

```
#[AsPayload(
    path: 'env::SEMITEXA_GRAPHQL_ROUTE_PATH::/graphql',
    methods: ['POST'],
    name: 'graphql.endpoint',
    /* … */
)]
```

This is the same `env::VAR::default` format the framework already uses for any `#[AsPayload]` route — `EnvValueResolver` substitutes the value during route discovery, and falls back to the inline default when the variable is unset. See `packages/semitexa-core/docs/PAYLOAD_ENV_ROUTE_OVERRIDES.md` for the framework-wide reference.

To customise the public route in a deployment, set `SEMITEXA_GRAPHQL_ROUTE_PATH` in `.env` (or in the process environment). Common values:

```
# .env
SEMITEXA_GRAPHQL_ROUTE_PATH=/api/graphql
# or /internal/graphql, /admin/graphql, etc.
```

Leave the variable unset (or commented) to keep the default `/graphql`. Verify the registered route at any time with `bin/semitexa routes:list | grep graphql`.

How `Payload → Handler → Resource` is preserved
-----------------------------------------------

[](#how-payload--handler--resource-is-preserved)

Resolvers in this package are intentionally thin. For each `#[ExposeAsGraphql]` field, the resolver:

1. takes the GraphQL arguments,
2. hydrates a fresh instance of the Payload DTO via setters (`PayloadHydratorInterface`); each setter validates its argument and may throw `Semitexa\Core\Exception\ValidationException`, which surfaces as a `VALIDATION_FAILED` GraphQL error,
3. resolves the bound Handler from the application container, including its `#[InjectAsReadonly]` dependencies (`HandlerInvokerInterface`),
4. instantiates the Resource declared by the route,
5. invokes `Handler::handle($payload, $resource)`,
6. serializes the returned Resource for GraphQL (`ResourceSerializerInterface`).

Business logic stays in Handlers. The GraphQL layer is a transport.

Exposing a Semitexa operation as GraphQL
----------------------------------------

[](#exposing-a-semitexa-operation-as-graphql)

Add `#[ExposeAsGraphql]` to any Payload DTO that already has `#[AsPayload]`:

```
#[AsPayload(
    path: '/graphql-demo/articles',
    methods: ['GET'],
    name: 'graphql.demo.articles.list',
    responseWith: ArticleCollectionResource::class,
)]
#[ExposeAsGraphql(
    field: 'articles',
    rootType: 'query',
    output: Article::class,
    description: 'List demo articles.',
    list: true,
)]
final class ArticleListQueryPayload { /* … */ }
```

That's all that's required. Discovery picks it up at boot, the schema builder produces a `[Article]` field on the root `Query` type, and the existing `ArticleListQueryHandler` (declared via `#[AsPayloadHandler(payload: …, resource: …)]`) runs at field resolution.

### Attribute reference

[](#attribute-reference)

ArgumentTypeDefaultMeaning`field`stringrequiredGraphQL field name (e.g. `articles`, `createArticle`).`rootType`string`'query'``'query'` or `'mutation'`.`output`?string`null`FQCN of the typed output DTO (e.g. `Article::class`). When `null`, the field's output type is the catch-all `Json` scalar.`description`string`''`Schema description (surfaces in introspection).`list`bool`false`When `true`, the schema field type is wrapped as `[Output]`.Argument mapping
----------------

[](#argument-mapping)

Each `public function setX(): void` setter on the Payload becomes one GraphQL argument named `x`.

- `string`, `int`, `float`, `bool` → `String`, `Int`, `Float`, `Boolean`.
- Setters named `setId` / `setSlug` / `setUuid` are exposed as `ID!` (non-null `ID`).
- Other arguments default to nullable so Payloads only need to set the fields the client actually supplied.
- Setters with non-scalar parameters are skipped — input objects / nested arguments are tracked under `ep-graphql-nested-resources`.

Output mapping
--------------

[](#output-mapping)

For each `output:` class:

- Each `public readonly` scalar property becomes a GraphQL field of the same name.
- `string`, `int`, `float`, `bool`, `?T` → standard scalars; nullable types stay nullable.
- Nested objects, lists of objects, embedded resources are not modeled in this iteration — see `ep-graphql-nested-resources`.

The Resource serializer reads the Resource's render context. The convention is: prefer the `data` key when present (matches every JSON Resource in the framework), otherwise return the whole render context. This makes existing Handlers work without changes — the same `Resource` you serve over REST renders correctly under GraphQL.

How to run the endpoint
-----------------------

[](#how-to-run-the-endpoint)

Start the application (`bin/semitexa server:start`) and POST to `/graphql`:

```
curl -s -X POST http://localhost:8080/graphql \
  -H 'Content-Type: application/json' \
  --data '{
    "query": "query { articles { id title published } }",
    "variables": null,
    "operationName": null
  }' | jq .
```

The response shape is the standard GraphQL-over-HTTP envelope:

```
{
  "data": { "articles": [/* … */] }
}
```

Or, on failure:

```
{
  "errors": [
    {
      "message": "Article #missing not found.",
      "extensions": { "code": "NOT_FOUND", "http_status": 404 },
      "locations": [{ "line": 1, "column": 9 }],
      "path": ["articleById"]
    }
  ],
  "data": { "articleById": null }
}
```

How errors are shaped
---------------------

[](#how-errors-are-shaped)

Errors carry a stable `extensions.code`. Mapping table:

Trigger`extensions.code`HTTP echoGraphQL parse / validation failure`GRAPHQL_VALIDATION`n/a`Semitexa\Core\Exception\ValidationException``VALIDATION_FAILED`422`Semitexa\Core\Exception\NotFoundException``NOT_FOUND`404`Semitexa\Core\Exception\AccessDeniedException``FORBIDDEN`403`Semitexa\Core\Exception\AuthenticationException``UNAUTHENTICATED`401`Semitexa\Core\Exception\RateLimitException``RATE_LIMITED`429any other `Throwable``INTERNAL_SERVER_ERROR`500The runtime never leaks original exception messages or stack traces for `INTERNAL_SERVER_ERROR` — the response carries a generic message.

HTTP status conventions
-----------------------

[](#http-status-conventions)

The endpoint always returns `200 OK` for executed-but-failed operations (errors live in the response body — the GraphQL way). Pre-execution shape failures (missing `query`, malformed JSON) flow through Core's regular validation pipeline and surface as `422`/`400`.

Current limitations
-------------------

[](#current-limitations)

- **No nested resources.** Output types are scalar-only objects; nested objects and lists of nested objects are not in the schema yet. Tracked: `ep-graphql-nested-resources`.
- **No subscriptions, no batching, no persisted queries, no federation.** Out of scope.
- **No `DataLoader`-style batching of resolvers.** Each field runs its full Payload→Handler→Resource cycle.
- **No GET execution.** Only `POST /graphql` is supported. GET-based execution and persisted queries are out of scope for the package's HTTP route.
- Field selection is tracked by webonyx but not pushed down to the resolver — the Handler always produces the full Resource, and webonyx then projects the requested fields. That's transparent to clients but means you cannot use unknown selections to skip work in the Handler.

Running the tests
-----------------

[](#running-the-tests)

```
# Package tests only
bin/semitexa test:run --testsuite semitexa-graphql

# Filter by name
bin/semitexa test:run --filter "Graphql"

# Full suite
bin/semitexa test:run
```

Tests cover: attribute behaviour, registry discovery, scalar mapping, schema generation, payload hydration, handler dispatch, resource serialization, error mapping, and end-to-end query/mutation execution against the demo Article domain.

###  Health Score

43

—

FairBetter than 90% of packages

Maintenance94

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 76.5% 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 ~4 days

Total

9

Last Release

53d ago

Major Versions

0.1.3 → 2026.04.03.12402026-04-03

v0.1.4 → 2026.05.08.16402026-05-08

### Community

Maintainers

![](https://www.gravatar.com/avatar/e94044da9f4c76cc5cd09ef0c693a68065787e10d0d18edfeedbef3573a9163a?d=identicon)[syntaxwanderer](/maintainers/syntaxwanderer)

---

Top Contributors

[![syntaxwanderer](https://avatars.githubusercontent.com/u/1311643?v=4)](https://github.com/syntaxwanderer "syntaxwanderer (26 commits)")[![needalicense](https://avatars.githubusercontent.com/u/13330467?v=4)](https://github.com/needalicense "needalicense (6 commits)")[![tarashanych](https://avatars.githubusercontent.com/u/49571976?v=4)](https://github.com/tarashanych "tarashanych (2 commits)")

---

Tags

apibackenddeveloper-toolsgraphqlgraphql-clientgraphql-serverjavascriptlibrarymutationquerysdktypescriptschemagraphqlhandlerpayloadsemitexa

### Embed Badge

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

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

###  Alternatives

[nuwave/lighthouse

A framework for serving GraphQL from Laravel

3.5k11.4M112](/packages/nuwave-lighthouse)[overblog/graphql-bundle

This bundle provides tools to build a GraphQL server in your Symfony App.

7968.2M34](/packages/overblog-graphql-bundle)[aimeos/ai-admin-graphql

Aimeos Admin GraphQL API extension

1.0k106.6k7](/packages/aimeos-ai-admin-graphql)[mll-lab/graphql-php-scalars

A collection of custom scalar types for usage with https://github.com/webonyx/graphql-php

1474.5M37](/packages/mll-lab-graphql-php-scalars)[ivome/graphql-relay-php

A PHP port of GraphQL Relay reference implementation

271671.3k5](/packages/ivome-graphql-relay-php)[overblog/graphql-php-generator

GraphQL types generator

29522.9k](/packages/overblog-graphql-php-generator)

PHPackages © 2026

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