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

ActiveLibrary[API Development](/categories/api)

waaseyaa/graphql
================

GraphQL endpoint + schema introspection for Waaseyaa — optional/experimental L6 surface; see README for the primary JSON:API framing.

v0.1.0-alpha.256(1mo ago)07.5k↓90.1%1GPL-2.0-or-laterPHPPHP &gt;=8.5

Since Mar 13Pushed 1mo agoCompare

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

READMEChangelogDependencies (47)Versions (253)Used By (1)

waaseyaa/graphql
================

[](#waaseyaagraphql)

> **Alternative protocol — not the primary API surface.**
>
> Per the framework's API-surface consolidation (mission `api-surface-consolidation-jsonapi-primary-01KSEFTV`), the framework's primary API surface is **JSON:API** in `packages/api/`. `waaseyaa/graphql`remains supported as an **optional / experimental** L6 protocol adapter for distributions whose consumers need GraphQL. It is not bundled by `waaseyaa/full`; install it explicitly when your distribution chooses GraphQL.

**Layer 6 — Interfaces**

GraphQL endpoint for Waaseyaa with auto-generated schema from registered entity types.

`GraphQlEndpoint` accepts queries at the configured route (registered via `GraphQlRouteProvider`) and resolves them against `EntityTypeManagerInterface`-derived schemas. Connection-style pagination follows the Relay spec: `totalCount` reflects the full unfiltered dataset (matching JSON:API semantics — see #436), while `items` returns only the access-filtered subset. Field resolvers honour `FieldAccessPolicyInterface` so attribute-level access control matches the JSON:API surface.

Key classes: `GraphQlEndpoint`, `GraphQlRouteProvider`, `GraphQlServiceProvider`.

Status
------

[](#status)

- **Stability:** optional / experimental. The public API surface (`GraphQlServiceProvider`, the `/graphql` endpoint, the schema-loading mechanism, any documented resolvers / mutations) is frozen at its current shape. The framework cadence ships no new feature work for this package; community contributions are accepted under the same review bar.
- **Bundle membership:** suggested by `waaseyaa/full` (not required). To install: `composer require waaseyaa/graphql`.
- **Decision provenance:** API-surface consolidation by mission `api-surface-consolidation-jsonapi-primary-01KSEFTV`. JSON:API is declared the framework's primary API surface in `docs/specs/jsonapi.md`.

Implementation gotchas
----------------------

[](#implementation-gotchas)

- **Reference fields keep storage field names**: A field defined as `author_id` with type `entity_reference` produces a GraphQL field named `author_id` (not `author`). It resolves to the nested entity object but the field name includes the `_id` suffix.
- **List filter/sort fields are gated through field-level access (R14, audit A11)**: `EntityResolver::resolveList()` applies caller-supplied filter/sort arguments as raw storage conditions. Previously `total` and `items` were gated only by the entity-level `guard->canView()` predicate, so a field restricted per row by a dynamic `FieldAccessPolicy` (a classification/clearance field) was a presence/ordering oracle: filtering `filter: [{field: "classification_field", value: "secret"}]` returned that value's row count even though the caller could not read the field. `resolveList()` now excludes a row from BOTH the count loop and the item loop when any caller-supplied filter/sort field is view-`Forbidden` for it (`GraphQlAccessGuard::isFieldViewForbidden()`), value-independently (dropped because the caller may not READ the queried field, never because of its value), matching the REST `JsonApiController::index()` fix. Because `QueryApplier` runs sort+pagination in storage before that drop, a *sort* on a field view-`Forbidden` on any viewable matched row is additionally REJECTED (`EntityResolver::rejectForbiddenSort()` throws a `UserError`), so a Forbidden row can never occupy an observable pagination rank (the empty-vs-populated-page ordering oracle). Gated to the bound-account path; the system-context bypass keeps the raw storage `COUNT`. **Structural allowlist (R15, audit A11):** the residual the R14 entry flagged is now closed — `EntityResolver::assertQueryableFields()` runs at the top of `resolveList()` (before any storage query, unconditionally) and throws a `UserError` for any filter/sort field that is not a declared field or entity key, is in `ALWAYS_INTERNAL_FIELDS` (`pass`/`password`/`password_hash`), or is a declared field flagged `settings['internal'] => true`. This mirrors REST's `JsonApiController::validateQueryFields()` and closes the undeclared-`_data`-key oracle (which reached `json_extract('$.')`) and the `internal`-flagged-secret oracle (e.g. `User.two_factor_secret`), both of which R14's per-policy gate could not see. See `docs/specs/api-layer.md` "Field-access gate on filter/sort fields (audit R14)". Pinned by `EntityResolverFieldFilterOracleTest` (R14) and `EntityResolverStructuralFieldAllowlistTest` (R15).
- **Mutations require an authenticated account (R11)**: `GraphQlEndpoint::handle()` rejects any mutation operation (`create{Type}`/`update{Type}`/`delete{Type}`, any alias or `operationName`-selected mutation) for an unauthenticated (`AccountInterface::isAuthenticated() === false`) caller, for every HTTP method, BEFORE building the schema or invoking a resolver: a uniform error message `"Authentication required for mutation operations."` (no entity id/type ever named) and the mutation never executes. Queries are unaffected. This closes an anonymous existence oracle: `update{Type}`/`delete{Type}` distinguished "entity absent" ("Entity not found: {type}/{id}") from "entity exists but access denied", an anonymous or otherwise-unauthorized caller could enumerate entity ids by diffing the two messages even though every per-entity `AccessPolicyInterface` was itself correct. As defense-in-depth for the authenticated-but-unauthorized case (not blocked by the gate above), `EntityResolver::resolveUpdate()`/`resolveDelete()` now collapse an access-denied outcome, at BOTH the entity level AND the per-field `edit` level (the `assertFieldEditAccess()` loop is inside the collapse), into the SAME "Entity not found" error the absent-entity branch throws, mirroring `resolveSingle()`, which has always returned `null` uniformly for both cases. (The endpoint sets `statusCode` 401 internally, but `GraphQlRouter::handle()` currently hardcodes an HTTP-200 envelope regardless, a separate pre-existing issue, out of R11 scope.) See `docs/specs/api-layer.md` (2026-07-05 entry) for the full writeup.

###  Health Score

51

—

FairBetter than 95% of packages

Maintenance94

Actively maintained with recent releases

Popularity25

Limited adoption so far

Community15

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 53.1% 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 ~0 days

Total

252

Last Release

32d ago

PHP version history (3 changes)v0.1.0-alpha.2PHP &gt;=8.3

v0.1.0-alpha.9PHP &gt;=8.4

v0.1.0-alpha.176PHP &gt;=8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/25d0ff572e93e3461e5180a920725d65691fd1e15e2d914b254dbbc2d6c393bd?d=identicon)[jonesrussell](/maintainers/jonesrussell)

---

Top Contributors

[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (77 commits)")[![jonesrussell](https://avatars.githubusercontent.com/u/499552?v=4)](https://github.com/jonesrussell "jonesrussell (68 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

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

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

PHPackages © 2026

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