WooCommerce Dual API

Extending the infrastructure

This document is for maintainers of the dual-API engine itself (the build tooling and the engine-integration layer), not for code-API authors. It is intentionally a high-level map; the code itself is the primary source of truth for the details. Everything below lives in the WooCommerce Dual API plugin repository. Key entry points:

The engine-decoupling surface

The GraphQL engine (currently webonyx/graphql-php, vendored as Automattic\WooCommerce\Vendor\GraphQL\*) is treated as a replaceable implementation detail. The contract that makes this possible:

Generated code, and any public signature on an Api\Infrastructure\* class, may reference the Schema\* surface but never Vendor\GraphQL\* directly.

src/Api/Infrastructure/Schema/ is the single point of contact with the engine. Generated resolvers, types, and root types import only from there. This matters because plugins commit their generated trees to their own repos: routing every engine reference through this surface means a future engine swap doesn’t break already-committed plugin code. Method bodies may touch vendor symbols: that’s the engine’s concern when the engine changes, not the plugin’s.

The surface uses three patterns (see Schema/README.md):

Adding a symbol to the surface

  1. Add a subclass / facade method / alias in the matching style.
  2. Update the template that needs it to import from Api\Infrastructure\Schema\*.
  3. Regenerate the test fixture (composer build:api:test); confirm the DummyApiAutogenerated/ diff is imports-only.
  4. Add a row to the table in Schema/README.md.

Versioning is implicit in the namespace. If a change would break already-committed plugin code, add a sibling namespace (e.g. Schema\V2) and teach the templates to emit against it; keep the current surface until the last dependent plugin migrates. An engine-migration checklist lives in Schema/README.md.

ApiBuilder (in brief)

ApiBuilder scans the code-API directory, reflects over each class (placement, type declarations, attributes), and renders the matching template into the output tree. It also:

It is not unit-tested directly; it’s validated end-to-end against a comprehensive dummy code-API fixture under tests/php/src/Internal/Api/Fixtures/DummyApi/, whose generated output is committed alongside it. When you change the builder or templates, update the dummy API if needed, regenerate the fixture (composer build:api:test), and run the test suite (composer test). Treat a non-imports-only diff in the generated tree as a signal to review. composer build:api:check is the staleness check for the fixture, and CI runs it.

Runtime helpers

What stays internal

QueryCache, Settings, the endpoint registrar, the query depth/complexity rules, and PluginLoader remain under Internal\Api\*. No external code references them; they’re wired by Main and the plugin bootstrap. Keep them there unless an external consumer genuinely needs them - at which point move only the public-facing surface, following the same engine-decoupling rule.

The vendored engine

lib/packages/GraphQL/ is webonyx/graphql-php re-namespaced with Mozart; lib/README.md in the plugin repository describes how to update it and the audit to run afterwards.