API groups
Available since version 10.2. Applications without groups work as before: everything belongs to the default general group.
Groups let you have several RPC APIs in one application — for example, a public one for clients, an internal one for the admin panel and separate BFFs. Each group has its own address, set of methods, versions, documentation and, if needed, its own token validation.
Entry points
Import the bundle routes once for each API. prefix sets the address, and defaults.api_group sets the group (without it — general):
# config/routes/ufo_json_rpc.yaml
ufo_json_rpc:
resource: ../../vendor/ufo-tech/json-rpc-bundle/config/router.yaml
prefix: /rpc
trailing_slash_on_root: false
ufo_json_rpc_internal:
resource: ../../vendor/ufo-tech/json-rpc-bundle/config/router.yaml
prefix: /rpc/internal
defaults:
api_group: internal
trailing_slash_on_root: false
ufo_json_rpc_public:
resource: ../../vendor/ufo-tech/json-rpc-bundle/config/router.yaml
prefix: /rpc/public
defaults:
api_group: public
trailing_slash_on_root: false
POST /rpc/internal executes a request, GET /rpc/internal returns OpenRPC, GET /rpc/internal/postman returns Postman; versioned addresses are /rpc/internal/v2 and so on. A group's documentation contains only its own methods and versions.
Route names get the group prefix: general_ufo_rpc_api_server, internal_ufo_rpc_api_server, … The old names without a prefix remain aliases for general, so router->generate('ufo_rpc_api_server') works as before.
#[RPC\APIGroups]
The attribute on a class or method defines which groups the method belongs to:
public function __construct(array $groups = [], bool $withDefault = true)
withDefault: true adds the general group, false does not.
use Ufo\JsonRpcBundle\ApiMethod\Interfaces\IRpcService;
use Ufo\RpcObject\RPC;
#[RPC\Info('catalog')]
#[RPC\APIGroups(['internal', 'public'], false)]
class CatalogApi implements IRpcService
{
public function list(): array { /* ... */ } // internal and public
#[RPC\APIGroups(['internal'], false)]
public function updatePrice(int $productId, int $price): true { return true; } // internal only
#[RPC\APIGroups(['public'], false)]
public function featured(): array { return [1]; } // public only
#[RPC\APIGroups()]
public function status(): string { return 'ok'; } // general only
#[RPC\APIGroups(['internal'], false)]
#[RPC\IgnoreApi]
public function rebuildIndex(): true { return true; } // not published anywhere
}
| Method | /rpc (general) |
/rpc/internal |
/rpc/public |
|---|---|---|---|
catalog.list |
— | yes | yes |
catalog.updatePrice |
— | yes | — |
catalog.featured |
— | — | yes |
catalog.status |
yes | — | — |
catalog.rebuildIndex |
— | — | — |
Rules:
- a method attribute fully replaces the class groups; without attributes a method belongs to
general; APIGroups([], false)does not publish the method in any group;#[RPC\IgnoreApi]takes precedence over groups;generalis not "all groups" but an ordinary default group;- method maps and versions are separated by group: a method from another group returns
Method not found; - for HTTP the group is determined by the route, not by a field in the JSON.
Tokens for groups
Without additional configuration, all groups are checked by the base validator (security.clients_tokens or your ITokenValidator). Separate validation for groups is set with security.providers. Each provider has exactly one of the service or clients_tokens parameters and a non-empty api_groups.
ufo_json_rpc:
security:
protected_api: true
token_name: 'Ufo-RPC-Token'
clients_tokens:
- '%env(resolve:UFO_API_TOKEN)%' # general and groups without a provider
providers:
internal:
api_groups: [internal]
clients_tokens:
- '%env(resolve:UFO_INTERNAL_API_TOKEN)%'
public:
service: App\Security\UserTokenValidator # implements ITokenValidator
api_groups: [public]
- A provider replaces the base validation for its groups; if it rejects a token, the base validation is not run.
- Two providers for the same group, a missing service or a class without
ITokenValidatorcause a container compilation error. protected_api,protected_docandtoken_nameare shared by all groups; protected documentation is checked by the provider of its group.
Console and async
bin/console ufo:rpc:process '{"jsonrpc":"2.0","id":1,"method":"catalog.updatePrice","params":{"productId":1,"price":100}}' \
--api-group=internal --ver=v1
Batch child processes keep the group and version of the route. For Symfony Messenger the group is passed explicitly:
$message = new \Ufo\RpcObject\RpcAsyncRequest($request, $token, apiGroup: 'internal');
Messages without a group are executed in general.
Translations for a group
Method names and descriptions in the documentation can be overridden for a specific group with translations/rpc/<group>.<locale>.yaml files. See API documentation.