Extensions

Extensions

What can be changed or added in REST Bundle without changing its code. Other packages of the ecosystem are listed on the JsonRpcBundle Extensions page.

RPC events

A REST call fires the same events as JSON-RPC (see Lifecycle), so an application listener works for both protocols:

use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Ufo\JsonRpcBundle\EventDrivenModel\Events\RpcEvent;
use Ufo\JsonRpcBundle\EventDrivenModel\Events\RpcPostResponseEvent;

#[AsEventListener(event: RpcEvent::POST_RESPONSE)]
final class AuditListener
{
    public function __invoke(RpcPostResponseEvent $event): void
    {
        // $event->rpcRequest->getMethod(), $event->response, $event->service
    }
}

To tell a REST call from a JSON-RPC one, check the carrier: RequestCarrier::getCarrier() instanceof Ufo\RestBundle\Services\RpcFromRest.

Replacing the request parser

The controller depends on Ufo\RestBundle\Interfaces\RestRequestParserInterface with a single method parse(string $routeName, Request $request): Service. Extend RestRequestParser and alias the interface:

# config/services.yaml
Ufo\RestBundle\Interfaces\RestRequestParserInterface: '@App\Rest\TenantAwareParser'
use Symfony\Component\HttpFoundation\Request;
use Ufo\JsonRpcBundle\Server\ServiceMap\Service;
use Ufo\RestBundle\Services\RestRequestParser;

final class TenantAwareParser extends RestRequestParser
{
    public function parse(string $routeName, Request $request): Service
    {
        // for example, reject a request without a tenant header before the call
        return parent::parse($routeName, $request);
    }
}

The parser must leave the carrier in RequestCarrier: the controller executes exactly RequestCarrier::getCarrier(). Exceptions implementing Ufo\RestBundle\Exceptions\RestErrorInterface are returned with a status equal to their code; the rest go through RestErrorNormalizer.

Reading the REST map

Ufo\RestBundle\Services\RestServiceMap returns the REST routes and RPC services of the current API group (from ApiGroupContext):

foreach ($restServiceMap->getVersions() as $version) {
    foreach ($restServiceMap->getRoutes($version) as $name => $route) {
        $service = $restServiceMap->getService($name, $version); // RPC Service
        // $route->getPath(), $route->getMethods()
    }
}

getRoute(), getService() and the methods called with an unknown version throw RestResourceNotFoundException.

Translations from a bundle

A bundle that provides RPC methods can also provide their titles and descriptions; REST documents pick them up (since 1.3):

public function prepend(ContainerBuilder $container): void
{
    $container->prependExtensionConfig('ufo_json_rpc', [
        'translations' => ['paths' => [__DIR__.'/../../translations']],
    ]);
}

Another adapter alongside REST

An adapter that imports its own routes file for each API group implements Ufo\JsonRpcBundle\Routing\IApiRouteImport (autoconfiguration, tag ufo.rpc.route_import), the same as RestRouteImport:

use Ufo\JsonRpcBundle\Routing\IApiRouteImport;

final class AcmeRouteImport implements IApiRouteImport
{
    public function getResource(): string { return __DIR__.'/../../config/router.yaml'; }
    public function getRouteNamePrefixAttribute(): string { return '_acme_name_prefix'; }
}

Do not extend the JsonRpcBundle route loader for this. A minimal adapter with RpcExecutor is described on the JsonRpcBundle For developers page.