REST Platform Integration Guide

This guide is for engineers wiring PHPNomad controllers into a specific PHP platform with an HTTP runtime. If you’ve never extended PHPNomad before, start here.

By the end, you’ll have a thin adapter that lets controllers run unchanged on your platform. It will register controllers with your router, translate your platform’s request into PHPNomad’s Request, convert controller Response objects back into your platform’s response, and keep error handling consistent.

You are responsible for a small contract:

This is in great shape—clear, concrete, and the two integration styles (platform-led vs strategy-led) come through nicely. What’s still missing or underspecified are a few “contract” rules that first-time extenders won’t intuit:

Below are short, drop-in blocks you can add to the intro section (kept skimmable, with bullets only where they carry weight).

Integration contract

To integrate with PHPNomad, your adapter must follow these rules.

Lifecycle (required order)

The request must flow in this order. Don’t skip or reorder steps.

Route → Middleware → Controller → Response → Interceptors

Endpoint schema (portable)

Controllers declare endpoints like /widgets/{id} using only literals and named params. Your adapter translates to the platform’s DSL and injects captured values back into Request under the same keys.

Examples

See these existing adapters for reference implementations.

Approach

The key to setting up PHPNomad's REST implementation with a platform is that you have to implement the Request, Response, and RestStrategy, and usually Auth as well.

These are all baked into existing platforms, so usually implementing these feels more like adapting the existing platform into PHPNomad's syntax.

For example in the WordPress integration's RestStrategy, it registers the route like this:

public function registerRoute(callable $controllerGetter)
{
    // Register the route with WordPress.
    add_action('rest_api_init', function () use ($controllerGetter) {
        /** @var Controller $controller */
        $controller = $controllerGetter();
        // Use WordPress's register_rest_route function to register the route.
        register_rest_route(
            $this->restNamespaceProvider->getRestNamespace(),
            $this->convertEndpointFormat($controller->getEndpoint()),
            [
                'methods' => $controller->getMethod(),
                'callback' => fn(WP_REST_Request $request) => $this->handleRequest($controller, new WordPressRequest($request, $this->currentUserResolver->getCurrentUser())),
                'permission_callback' => '__return_true'
            ]
        );
    });
}

The handleRequest method that actually does the work of adapting the PHPNomad request into a WordPress request. It does this by passing a WordPressRequest object, which implements PHPNomad's Request interface. handleRequest then runs the controller and converts the response back into a WordPress response.

You can see it in action here., but the guts of it are in this method, which runs middleware, gets the response, and runs interceptors:

private function wrapCallback(Controller $controller, Request $request): Response
{
    // Maybe process middleware.
    if ($controller instanceof HasMiddleware) {
        Arr::each($controller->getMiddleware($request), fn(Middleware $middleware) => $middleware->process($request));
    }


    /** @var \PHPNomad\Integrations\WordPress\Rest\Response $response */
    $response = $controller->getResponse($request);


    // Maybe process interceptors.
    if ($controller instanceof HasInterceptors) {
        Arr::each($controller->getInterceptors($request, $response), fn(Interceptor $interceptor) => $interceptor->process($request, $response));
    }


    return $response;
}

In some cases, the integration is more-loosely built. For example, the fastroute integration specifically targets making fastroute natively use PHPNomad to handle building a minimalistic REST API that uses PHPNomad.

This requires a bit more code to accomplish, since Fastroute doesn't handle a lot of the necessary aspects for us. In the case of Fastroute, we created our own registry to store the routes so that we can reference that and handle routing ourselves. As shown above, other platforms like WordPress or Laravel would handle most of this for us.

Fastroute's registerRoute method looks like this:

public function registerRoute(callable $controllerGetter)
{
    $this->registry->set(function () use ($controllerGetter) {
        /** @var Controller $controller */
        $controller = $controllerGetter();


        return [
          $controller->getMethod(),
          $controller->getEndpoint(),
          function ($request) use ($controller) {
              if ($controller instanceof HasMiddleware) {
                  $this->runMiddleware($controller, $request);
              }
              $response = $controller->getResponse($request);
              $this->setRestHeaders($response);


              if ($controller instanceof HasInterceptors) {
                  $this->runInterceptors($controller, $request, $response);
              }


              return $response;
          }
        ];


    });
}

The main difference here is that we needed to provide our own routing registry, and we also needed to handle setting the response headers, since Fastroute doesn't do that for us, however the rest of the logic is very similar to the WordPress example above.